@arex95/vue-core 1.1.35 → 1.1.37

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/dist/index.mjs CHANGED
@@ -1,11 +1,182 @@
1
1
  import axios from 'axios';
2
+ import { jwtDecode } from 'jwt-decode';
2
3
  import { useRouter } from 'vue-router';
3
4
  import { v4 } from 'uuid';
4
5
  import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
5
- import { jwtDecode } from 'jwt-decode';
6
6
  import { useQuery } from '@tanstack/vue-query';
7
7
  import { ref, watch, onServerPrefetch, onMounted, computed } from 'vue';
8
8
 
9
+ let tokensConfig = Object.freeze({
10
+ ACCESS_TOKEN: "access_token",
11
+ REFRESH_TOKEN: "refresh_token",
12
+ });
13
+ /**
14
+ * Configures the global keys for access and refresh tokens.
15
+ * Once set, they cannot be modified.
16
+ *
17
+ * @param {TokenKeyConfig} config - An object containing the token keys.
18
+ * @param {string} config.accessTokenKey - The name of the key for the access token.
19
+ * @param {string} config.refreshTokenKey - The name of the key for the refresh token.
20
+ *
21
+ * @returns {void} Does not return anything, but freezes the token configuration object.
22
+ */
23
+ function configTokenKeys(config) {
24
+ tokensConfig = Object.freeze({
25
+ ACCESS_TOKEN: config.accessTokenKey,
26
+ REFRESH_TOKEN: config.refreshTokenKey,
27
+ });
28
+ }
29
+ /**
30
+ * Retrieves the current token configuration.
31
+ *
32
+ * @returns {TokensConfig} The configuration of the access and refresh token keys.
33
+ */
34
+ function getTokenConfig() {
35
+ return tokensConfig;
36
+ }
37
+
38
+ /**
39
+ * Converts an ArrayBuffer or Uint8Array to a hexadecimal string.
40
+ * @param buffer The ArrayBuffer or Uint8Array to convert.
41
+ * @returns The hexadecimal string.
42
+ */
43
+ function ab2hex(buffer) {
44
+ return Array.from(new Uint8Array(buffer))
45
+ .map((byte) => byte.toString(16).padStart(2, "0"))
46
+ .join("");
47
+ }
48
+ /**
49
+ * Converts a hexadecimal string to a Uint8Array.
50
+ * @param hex The hexadecimal string to convert.
51
+ * @returns The Uint8Array.
52
+ * @throws {TypeError} If the input is not a string.
53
+ * @throws {Error} If the hexadecimal string format is invalid or has an odd length.
54
+ */
55
+ function hex2ab(hex) {
56
+ if (typeof hex !== "string") {
57
+ throw new TypeError("Input must be a string.");
58
+ }
59
+ if (hex.length === 0) {
60
+ return new Uint8Array();
61
+ }
62
+ if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
63
+ throw new Error("Invalid hexadecimal string format or odd length.");
64
+ }
65
+ const array = new Uint8Array(hex.length / 2);
66
+ for (let i = 0; i < hex.length; i += 2) {
67
+ array[i / 2] = parseInt(hex.substring(i, i + 2), 16);
68
+ }
69
+ return array;
70
+ }
71
+ /**
72
+ * Derives an encryption key from a secret key.
73
+ * @param secretKey The secret key in plain text.
74
+ * @returns A promise that resolves with the derived CryptoKey.
75
+ * @throws {Error} If the secretKey is null or empty.
76
+ */
77
+ async function importKey(secretKey) {
78
+ if (!secretKey) {
79
+ throw new Error("Secret key cannot be null or empty.");
80
+ }
81
+ const keyMaterial = new TextEncoder().encode(secretKey);
82
+ const digest = await crypto.subtle.digest("SHA-256", keyMaterial);
83
+ return crypto.subtle.importKey("raw", digest, { name: "AES-CBC", length: 256 }, false, ["encrypt", "decrypt"]);
84
+ }
85
+ /**
86
+ * Encrypts a value with the provided secret key.
87
+ * @param value The value to encrypt.
88
+ * @param secretKey The secret key for encryption.
89
+ * @returns A promise that resolves with the IV (hex) + ciphertext (hex) string.
90
+ * @throws {Error} If the secretKey is null or empty (via importKey).
91
+ */
92
+ async function encrypt(value, secretKey) {
93
+ const key = await importKey(secretKey);
94
+ const iv = crypto.getRandomValues(new Uint8Array(16));
95
+ const encodedValue = new TextEncoder().encode(value);
96
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, key, encodedValue);
97
+ return ab2hex(iv) + ab2hex(new Uint8Array(ciphertext));
98
+ }
99
+ /**
100
+ * Decrypts an encrypted value.
101
+ * @param encryptedValue The encrypted string (IV_hex + ciphertext_hex).
102
+ * @param secretKey The secret key for decryption.
103
+ * @returns A promise that resolves with the decrypted value.
104
+ * @throws {Error} If encryptedValue is null or empty, too short,
105
+ * or if the IV/ciphertext have incorrect lengths after conversion.
106
+ * @throws {Error} If the secretKey is null or empty (via importKey).
107
+ * @throws {TypeError} If hex2ab receives an invalid input type.
108
+ */
109
+ async function decrypt(encryptedValue, secretKey) {
110
+ if (!encryptedValue) {
111
+ throw new Error("Encrypted value cannot be null or empty.");
112
+ }
113
+ // For AES-CBC, the IV is ALWAYS 16 bytes.
114
+ // 16 bytes * 2 hex characters/byte = 32 hex characters for the IV.
115
+ if (encryptedValue.length < 32) {
116
+ throw new Error("Encrypted value is too short. Expected at least 32 hexadecimal characters for the IV.");
117
+ }
118
+ const key = await importKey(secretKey);
119
+ const ivHex = encryptedValue.substring(0, 32);
120
+ const ciphertextHex = encryptedValue.substring(32);
121
+ const iv = hex2ab(ivHex);
122
+ const ciphertext = hex2ab(ciphertextHex);
123
+ if (iv.byteLength !== 16) {
124
+ throw new Error(`Converted IV has incorrect length: ${iv.byteLength} bytes. Expected 16 bytes.`);
125
+ }
126
+ if (ciphertext.byteLength === 0) {
127
+ throw new Error("Ciphertext is empty. No data to decrypt.");
128
+ }
129
+ const decryptedBuffer = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, ciphertext);
130
+ return new TextDecoder().decode(decryptedBuffer);
131
+ }
132
+
133
+ /**
134
+ * Encrypts and stores an item in local or session storage.
135
+ * Assumes the `window` environment is available.
136
+ * @param key The key under which to store the value.
137
+ * @param value The value to encrypt and store.
138
+ * @param secretKey The secret key for encryption.
139
+ * @param location Determines where the item is stored: 'local' for localStorage, 'session' for sessionStorage.
140
+ * @returns A promise that resolves when the item is stored. Throws an error if it fails.
141
+ */
142
+ async function storeEncryptedItem(key, value, secretKey, location) {
143
+ if (typeof window === "undefined") {
144
+ throw new Error("Cannot access storage: window is not defined.");
145
+ }
146
+ const storage = location === "local" ? window.localStorage : window.sessionStorage;
147
+ const encryptedValue = await encrypt(value, secretKey);
148
+ storage.setItem(key, encryptedValue);
149
+ }
150
+ /**
151
+ * Retrieves and decrypts a value from local or session storage.
152
+ * Assumes the `window` environment is available.
153
+ * @param key The key of the item to retrieve.
154
+ * @param secretKey The secret key for decryption.
155
+ * @param location Specifies where to search for the item: 'local' for localStorage, 'session' for sessionStorage, or 'any' to check both (session first).
156
+ * @returns A promise that resolves with the decrypted value or null if not found or decryption fails.
157
+ */
158
+ async function getDecryptedItem(key, secretKey, location) {
159
+ if (typeof window === "undefined") {
160
+ return null;
161
+ }
162
+ let encryptedData = null;
163
+ if (location === "session" || location === "any") {
164
+ encryptedData = window.sessionStorage.getItem(key);
165
+ }
166
+ if (!encryptedData && (location === "local" || location === "any")) {
167
+ encryptedData = window.localStorage.getItem(key);
168
+ }
169
+ if (!encryptedData) {
170
+ return null;
171
+ }
172
+ try {
173
+ return await decrypt(encryptedData, secretKey);
174
+ }
175
+ catch (error) {
176
+ return null;
177
+ }
178
+ }
179
+
9
180
  /**
10
181
  * Enum defining available screen sizes.
11
182
  */
@@ -538,35 +709,6 @@ function inferErrorType(error) {
538
709
  return 'error';
539
710
  }
540
711
 
541
- let tokensConfig = Object.freeze({
542
- ACCESS_TOKEN: "access_token",
543
- REFRESH_TOKEN: "refresh_token",
544
- });
545
- /**
546
- * Configures the global keys for access and refresh tokens.
547
- * Once set, they cannot be modified.
548
- *
549
- * @param {TokenKeyConfig} config - An object containing the token keys.
550
- * @param {string} config.accessTokenKey - The name of the key for the access token.
551
- * @param {string} config.refreshTokenKey - The name of the key for the refresh token.
552
- *
553
- * @returns {void} Does not return anything, but freezes the token configuration object.
554
- */
555
- function configTokenKeys(config) {
556
- tokensConfig = Object.freeze({
557
- ACCESS_TOKEN: config.accessTokenKey,
558
- REFRESH_TOKEN: config.refreshTokenKey,
559
- });
560
- }
561
- /**
562
- * Retrieves the current token configuration.
563
- *
564
- * @returns {TokensConfig} The configuration of the access and refresh token keys.
565
- */
566
- function getTokenConfig() {
567
- return tokensConfig;
568
- }
569
-
570
712
  let endpointsConfig = {
571
713
  LOGIN: "/login",
572
714
  REFRESH: "/refresh",
@@ -602,322 +744,20 @@ function getEndpointsConfig() {
602
744
  return endpointsConfig;
603
745
  }
604
746
 
747
+ let appKey = null;
605
748
  /**
606
- * AxiosService class encapsulates the Axios configuration and logic.
607
- * It manages request and response interceptors and provides methods for making HTTP requests.
749
+ * Sets the main application encryption key.
750
+ * This key is expected to be used for encryption purposes within the application.
751
+ *
752
+ * @param {AppKeyConfig} config - An object containing the application encryption key.
753
+ * @param {string} config.key - The new application encryption key.
754
+ *
755
+ * @returns {void} Does not return anything, but updates the application key.
756
+ * @throws {Error} If the provided key is null, undefined, or an empty string.
608
757
  */
609
- class AxiosService {
610
- instance;
611
- cancelTokenSource;
612
- activeRequests = 0;
613
- refreshTokenInProgress = false;
614
- refreshTokenUrl;
615
- /**
616
- * Initializes the AxiosService instance by creating an Axios instance with default configuration
617
- * and setting up request and response interceptors.
618
- */
619
- constructor(url, headers = {}) {
620
- this.cancelTokenSource = axios.CancelToken.source();
621
- this.instance = axios.create({
622
- baseURL: url ?? '',
623
- timeout: 300000,
624
- headers: {
625
- 'Accept': 'application/json',
626
- ...headers,
627
- },
628
- withCredentials: false,
629
- });
630
- const endpointsConfig = getEndpointsConfig();
631
- this.refreshTokenUrl = endpointsConfig.REFRESH;
632
- this.initializeInterceptors();
633
- }
634
- /**
635
- * Initializes request and response interceptors for the Axios instance.
636
- */
637
- initializeInterceptors() {
638
- const { ACCESS_TOKEN, REFRESH_TOKEN } = getTokenConfig();
639
- this.instance.interceptors.request.use((config) => {
640
- const token = typeof window !== 'undefined' ? localStorage.getItem(ACCESS_TOKEN) : null;
641
- if (token && config.headers) {
642
- config.headers.Authorization = `Bearer ${token}`;
643
- }
644
- config.cancelToken = this.cancelTokenSource.token;
645
- this.activeRequests++;
646
- return config;
647
- }, (error) => {
648
- handleError(error, false);
649
- this.activeRequests++;
650
- return Promise.reject(error);
651
- });
652
- this.instance.interceptors.response.use((response) => {
653
- this.activeRequests--;
654
- return response;
655
- }, async (error) => {
656
- this.activeRequests--;
657
- // Check for unauthorized access (401)
658
- if (axios.isAxiosError(error) && error.response?.status === 401 && !this.refreshTokenInProgress) {
659
- const refreshToken = localStorage.getItem(REFRESH_TOKEN);
660
- if (refreshToken) {
661
- this.refreshTokenInProgress = true;
662
- try {
663
- // Attempt to refresh the token
664
- const refreshResponse = await axios.post(this.refreshTokenUrl, { refreshToken });
665
- const { token: newAccessToken } = refreshResponse.data;
666
- if (newAccessToken) {
667
- // Save the new access token
668
- localStorage.setItem(ACCESS_TOKEN, newAccessToken);
669
- // Retry the original request with the new token
670
- if (error.config && error.config.headers) {
671
- error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
672
- }
673
- this.refreshTokenInProgress = false;
674
- if (error.config) {
675
- return this.instance(error.config);
676
- }
677
- }
678
- }
679
- catch (refreshError) {
680
- handleError(refreshError, false);
681
- this.refreshTokenInProgress = false;
682
- localStorage.removeItem(ACCESS_TOKEN);
683
- localStorage.removeItem(REFRESH_TOKEN);
684
- }
685
- }
686
- }
687
- handleError(error, false);
688
- return Promise.reject(error);
689
- });
690
- }
691
- /**
692
- * Returns the number of active requests.
693
- */
694
- getActiveRequests() {
695
- return this.activeRequests;
696
- }
697
- /**
698
- * Returns the Axios instance with the configured settings and interceptors.
699
- * @returns {AxiosInstance} The configured Axios instance.
700
- */
701
- getAxiosInstance() {
702
- return this.instance;
703
- }
704
- /**
705
- * Cancels all ongoing requests.
706
- */
707
- cancelAllRequests() {
708
- this.cancelTokenSource.cancel('Operation canceled by the user.');
709
- this.cancelTokenSource = axios.CancelToken.source();
710
- }
711
- /**
712
- * Sets a new header for the Axios instance.
713
- * @param {string} key - The header key.
714
- * @param {string} value - The header value.
715
- */
716
- setHeader(key, value) {
717
- this.instance.defaults.headers.common[key] = value;
718
- }
719
- /**
720
- * Removes a header from the Axios instance.
721
- * @param {string} key - The header key to remove.
722
- */
723
- removeHeader(key) {
724
- delete this.instance.defaults.headers.common[key];
725
- }
726
- }
727
-
728
- let axiosInstance;
729
- /**
730
- * Configures the global Axios instance with a base URL.
731
- *
732
- * @param {AxiosConfig} config - An object containing the base URL for the Axios instance.
733
- * @param {string} config.baseURL - The base URL for the Axios instance.
734
- *
735
- * @returns {void}
736
- */
737
- const configAxios = (config) => {
738
- axiosInstance = new AxiosService(config.baseURL);
739
- };
740
- /**
741
- * Retrieves the configured Axios instance.
742
- *
743
- * @returns {AxiosService} The configured Axios instance.
744
- * @throws Will throw an error if the Axios instance is not configured.
745
- */
746
- const getAxiosInstance = () => {
747
- if (!axiosInstance) {
748
- throw new Error("Axios instance not configured. Call configAxios first.");
749
- }
750
- return axiosInstance.getAxiosInstance();
751
- };
752
- /**
753
- * Creates a new AxiosService instance with custom headers.
754
- *
755
- * @param {CustomAxiosConfig} config - An object containing the base URL and optional custom headers.
756
- * @param {string} config.baseURL - The base URL for the Axios instance.
757
- * @param {Record<string, string>} [config.headers] - Custom headers to set (optional).
758
- *
759
- * @returns {AxiosService} The new AxiosService instance.
760
- */
761
- const createCustomAxiosInstance = (config) => {
762
- return new AxiosService(config.baseURL, config.headers).getAxiosInstance();
763
- };
764
-
765
- /**
766
- * Converts an ArrayBuffer or Uint8Array to a hexadecimal string.
767
- * @param buffer The ArrayBuffer or Uint8Array to convert.
768
- * @returns The hexadecimal string.
769
- */
770
- function ab2hex(buffer) {
771
- return Array.from(new Uint8Array(buffer))
772
- .map((byte) => byte.toString(16).padStart(2, "0"))
773
- .join("");
774
- }
775
- /**
776
- * Converts a hexadecimal string to a Uint8Array.
777
- * @param hex The hexadecimal string to convert.
778
- * @returns The Uint8Array.
779
- * @throws {TypeError} If the input is not a string.
780
- * @throws {Error} If the hexadecimal string format is invalid or has an odd length.
781
- */
782
- function hex2ab(hex) {
783
- if (typeof hex !== "string") {
784
- throw new TypeError("Input must be a string.");
785
- }
786
- if (hex.length === 0) {
787
- return new Uint8Array();
788
- }
789
- if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
790
- throw new Error("Invalid hexadecimal string format or odd length.");
791
- }
792
- const array = new Uint8Array(hex.length / 2);
793
- for (let i = 0; i < hex.length; i += 2) {
794
- array[i / 2] = parseInt(hex.substring(i, i + 2), 16);
795
- }
796
- return array;
797
- }
798
- /**
799
- * Derives an encryption key from a secret key.
800
- * @param secretKey The secret key in plain text.
801
- * @returns A promise that resolves with the derived CryptoKey.
802
- * @throws {Error} If the secretKey is null or empty.
803
- */
804
- async function importKey(secretKey) {
805
- if (!secretKey) {
806
- throw new Error("Secret key cannot be null or empty.");
807
- }
808
- const keyMaterial = new TextEncoder().encode(secretKey);
809
- const digest = await crypto.subtle.digest("SHA-256", keyMaterial);
810
- return crypto.subtle.importKey("raw", digest, { name: "AES-CBC", length: 256 }, false, ["encrypt", "decrypt"]);
811
- }
812
- /**
813
- * Encrypts a value with the provided secret key.
814
- * @param value The value to encrypt.
815
- * @param secretKey The secret key for encryption.
816
- * @returns A promise that resolves with the IV (hex) + ciphertext (hex) string.
817
- * @throws {Error} If the secretKey is null or empty (via importKey).
818
- */
819
- async function encrypt(value, secretKey) {
820
- const key = await importKey(secretKey);
821
- const iv = crypto.getRandomValues(new Uint8Array(16));
822
- const encodedValue = new TextEncoder().encode(value);
823
- const ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, key, encodedValue);
824
- return ab2hex(iv) + ab2hex(new Uint8Array(ciphertext));
825
- }
826
- /**
827
- * Decrypts an encrypted value.
828
- * @param encryptedValue The encrypted string (IV_hex + ciphertext_hex).
829
- * @param secretKey The secret key for decryption.
830
- * @returns A promise that resolves with the decrypted value.
831
- * @throws {Error} If encryptedValue is null or empty, too short,
832
- * or if the IV/ciphertext have incorrect lengths after conversion.
833
- * @throws {Error} If the secretKey is null or empty (via importKey).
834
- * @throws {TypeError} If hex2ab receives an invalid input type.
835
- */
836
- async function decrypt(encryptedValue, secretKey) {
837
- if (!encryptedValue) {
838
- throw new Error("Encrypted value cannot be null or empty.");
839
- }
840
- // For AES-CBC, the IV is ALWAYS 16 bytes.
841
- // 16 bytes * 2 hex characters/byte = 32 hex characters for the IV.
842
- if (encryptedValue.length < 32) {
843
- throw new Error("Encrypted value is too short. Expected at least 32 hexadecimal characters for the IV.");
844
- }
845
- const key = await importKey(secretKey);
846
- const ivHex = encryptedValue.substring(0, 32);
847
- const ciphertextHex = encryptedValue.substring(32);
848
- const iv = hex2ab(ivHex);
849
- const ciphertext = hex2ab(ciphertextHex);
850
- if (iv.byteLength !== 16) {
851
- throw new Error(`Converted IV has incorrect length: ${iv.byteLength} bytes. Expected 16 bytes.`);
852
- }
853
- if (ciphertext.byteLength === 0) {
854
- throw new Error("Ciphertext is empty. No data to decrypt.");
855
- }
856
- const decryptedBuffer = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, ciphertext);
857
- return new TextDecoder().decode(decryptedBuffer);
858
- }
859
-
860
- /**
861
- * Encrypts and stores an item in local or session storage.
862
- * Assumes the `window` environment is available.
863
- * @param key The key under which to store the value.
864
- * @param value The value to encrypt and store.
865
- * @param secretKey The secret key for encryption.
866
- * @param location Determines where the item is stored: 'local' for localStorage, 'session' for sessionStorage.
867
- * @returns A promise that resolves when the item is stored. Throws an error if it fails.
868
- */
869
- async function storeEncryptedItem(key, value, secretKey, location) {
870
- if (typeof window === "undefined") {
871
- throw new Error("Cannot access storage: window is not defined.");
872
- }
873
- const storage = location === "local" ? window.localStorage : window.sessionStorage;
874
- const encryptedValue = await encrypt(value, secretKey);
875
- storage.setItem(key, encryptedValue);
876
- }
877
- /**
878
- * Retrieves and decrypts a value from local or session storage.
879
- * Assumes the `window` environment is available.
880
- * @param key The key of the item to retrieve.
881
- * @param secretKey The secret key for decryption.
882
- * @param location Specifies where to search for the item: 'local' for localStorage, 'session' for sessionStorage, or 'any' to check both (session first).
883
- * @returns A promise that resolves with the decrypted value or null if not found or decryption fails.
884
- */
885
- async function getDecryptedItem(key, secretKey, location) {
886
- if (typeof window === "undefined") {
887
- return null;
888
- }
889
- let encryptedData = null;
890
- if (location === "session" || location === "any") {
891
- encryptedData = window.sessionStorage.getItem(key);
892
- }
893
- if (!encryptedData && (location === "local" || location === "any")) {
894
- encryptedData = window.localStorage.getItem(key);
895
- }
896
- if (!encryptedData) {
897
- return null;
898
- }
899
- try {
900
- return await decrypt(encryptedData, secretKey);
901
- }
902
- catch (error) {
903
- return null;
904
- }
905
- }
906
-
907
- let appKey = null;
908
- /**
909
- * Sets the main application encryption key.
910
- * This key is expected to be used for encryption purposes within the application.
911
- *
912
- * @param {AppKeyConfig} config - An object containing the application encryption key.
913
- * @param {string} config.key - The new application encryption key.
914
- *
915
- * @returns {void} Does not return anything, but updates the application key.
916
- * @throws {Error} If the provided key is null, undefined, or an empty string.
917
- */
918
- function configAppKey(config) {
919
- if (!config || !config.appKey || config.appKey.trim() === "") {
920
- throw new Error("The application encryption key cannot be null or empty.");
758
+ function configAppKey(config) {
759
+ if (!config || !config.appKey || config.appKey.trim() === "") {
760
+ throw new Error("The application encryption key cannot be null or empty.");
921
761
  }
922
762
  appKey = config.appKey;
923
763
  }
@@ -1042,39 +882,712 @@ async function getSessionConfig() {
1042
882
  }
1043
883
 
1044
884
  /**
1045
- * Creates and downloads a file from Blob data.
1046
- * @param {Blob} blob The Blob containing the file data.
1047
- * @param {string} fileName The name of the file to create.
885
+ * Clears all stored authentication data (access and refresh tokens)
886
+ * from either sessionStorage, localStorage, or both based on the provided location preference.
887
+ *
888
+ * @param {LocationPreference} location - The storage preference ('local' for localStorage, 'session' for sessionStorage, 'any' for both).
889
+ * @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
1048
890
  */
1049
- function downloadFile(blob, fileName) {
1050
- const link = document.createElement('a');
1051
- const url = URL.createObjectURL(blob);
1052
- link.setAttribute('href', url);
1053
- link.setAttribute('download', fileName);
1054
- // Append link to the body and trigger a click
1055
- document.body.appendChild(link);
1056
- link.click();
1057
- // Clean up
1058
- document.body.removeChild(link);
1059
- URL.revokeObjectURL(url);
1060
- }
891
+ const cleanCredentials = async (location) => {
892
+ const tokensConfig = getTokenConfig();
893
+ Object.keys(tokensConfig).forEach((key) => {
894
+ const itemKey = tokensConfig[key];
895
+ if (location === "local" || location === "any") {
896
+ localStorage.removeItem(itemKey);
897
+ }
898
+ if (location === "session" || location === "any") {
899
+ sessionStorage.removeItem(itemKey);
900
+ }
901
+ });
902
+ };
1061
903
  /**
1062
- * Exports data to a CSV file.
1063
- * @param {string[]} headers The headers for the CSV.
1064
- * @param {any[][]} data The data to export, as an array of arrays.
1065
- * @param {string} fileName The name of the file to create.
904
+ * Retrieves the authentication token (access token) from storage, decrypting it
905
+ * using the provided secret key and based on the specified session preference.
906
+ *
907
+ * @param {string} secretKey - The secret key used for decryption.
908
+ * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
909
+ * @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
1066
910
  */
1067
- function exportToCSV(headers, data, fileName) {
1068
- const csvRows = [];
1069
- // Add the headers
1070
- csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
1071
- // Add the data rows
1072
- for (const row of data) {
1073
- csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
1074
- }
1075
- // Create a Blob with CSV data
1076
- const csvContent = csvRows.join('\n');
1077
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
911
+ const getAuthToken = async (secretKey, location) => {
912
+ const tokensConfig = getTokenConfig();
913
+ return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
914
+ };
915
+ /**
916
+ * Retrieves the authentication refresh token from storage, decrypting it
917
+ * using the provided secret key and based on the specified session preference.
918
+ *
919
+ * @param {string} secretKey - The secret key used for decryption.
920
+ * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
921
+ * @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
922
+ */
923
+ const getAuthRefreshToken = async (secretKey, location) => {
924
+ const tokensConfig = getTokenConfig();
925
+ return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
926
+ };
927
+ /**
928
+ * Stores the authentication token (access token) in storage after encrypting it,
929
+ * based on the specified session preference.
930
+ *
931
+ * @param {string} token - The access token to store.
932
+ * @param {string} secretKey - The secret key used for encryption.
933
+ * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
934
+ * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
935
+ */
936
+ const storeAuthToken = async (token, secretKey, location) => {
937
+ const tokensConfig = getTokenConfig();
938
+ await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
939
+ };
940
+ /**
941
+ * Stores the authentication refresh token in storage after encrypting it,
942
+ * based on the specified session preference.
943
+ *
944
+ * @param {string} token - The refresh token to store.
945
+ * @param {string} secretKey - The secret key used for encryption.
946
+ * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
947
+ * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
948
+ */
949
+ const storeAuthRefreshToken = async (token, secretKey, location) => {
950
+ const tokensConfig = getTokenConfig();
951
+ await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
952
+ };
953
+ /**
954
+ * Verifies the validity and expiration of the current authentication token.
955
+ * If the token is missing, invalid, or expired, appropriate errors are thrown and credentials are cleaned.
956
+ *
957
+ * @returns {Promise<boolean>} True if the token is valid and unexpired.
958
+ * @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
959
+ * "TOKEN_INVALID" if the token format is invalid.
960
+ */
961
+ const verifyAuth = async () => {
962
+ const sessionPersistence = 'any';
963
+ const handleAuthError = async (message, shouldClean = true) => {
964
+ handleError(message, false);
965
+ if (shouldClean) {
966
+ await cleanCredentials(sessionPersistence);
967
+ }
968
+ return false;
969
+ };
970
+ try {
971
+ const token = await getAuthToken(getAppKey(), sessionPersistence);
972
+ if (!token) {
973
+ return await handleAuthError("TOKEN_MISSING: No valid token found");
974
+ }
975
+ let decoded;
976
+ try {
977
+ decoded = jwtDecode(token);
978
+ }
979
+ catch (decodeError) {
980
+ return await handleAuthError("TOKEN_INVALID: Invalid token format");
981
+ }
982
+ const currentTime = Date.now() / 1000;
983
+ if (typeof decoded.exp !== "number") {
984
+ return await handleAuthError("TOKEN_INVALID: Invalid expiration format");
985
+ }
986
+ if (decoded.exp <= currentTime) {
987
+ return await handleAuthError("TOKEN_EXPIRED: Token is expired");
988
+ }
989
+ return true;
990
+ }
991
+ catch (error) {
992
+ return await handleAuthError("AUTH_ERROR: An unexpected error occurred", true);
993
+ }
994
+ };
995
+
996
+ class AxiosService {
997
+ instance;
998
+ cancelTokenSource;
999
+ activeRequests = 0;
1000
+ refreshTokenUrl;
1001
+ refreshAuth;
1002
+ isRefreshing = false;
1003
+ failedQueue = [];
1004
+ constructor(options, refreshAuth) {
1005
+ this.cancelTokenSource = axios.CancelToken.source();
1006
+ this.refreshAuth = refreshAuth;
1007
+ const endpointsConfig = getEndpointsConfig();
1008
+ this.refreshTokenUrl = endpointsConfig.REFRESH;
1009
+ this.instance = axios.create({
1010
+ baseURL: options.baseURL ?? "",
1011
+ timeout: options.timeout ?? 30000,
1012
+ headers: {
1013
+ Accept: "application/json",
1014
+ "Content-Type": "application/json",
1015
+ ...options.headers,
1016
+ },
1017
+ withCredentials: options.withCredentials ?? false,
1018
+ });
1019
+ this.initializeInterceptors();
1020
+ }
1021
+ processQueue(error, token = null) {
1022
+ this.failedQueue.forEach((prom) => {
1023
+ if (error) {
1024
+ prom.reject(error);
1025
+ }
1026
+ else if (token) {
1027
+ prom.resolve(token);
1028
+ }
1029
+ });
1030
+ this.failedQueue = [];
1031
+ }
1032
+ setAuthHeader(config, token) {
1033
+ if (config.headers) {
1034
+ config.headers.Authorization = `Bearer ${token}`;
1035
+ }
1036
+ }
1037
+ initializeInterceptors() {
1038
+ this.instance.interceptors.request.use(async (config) => {
1039
+ const token = await getAuthToken(getAppKey(), "any");
1040
+ if (token) {
1041
+ this.setAuthHeader(config, token);
1042
+ }
1043
+ config.cancelToken = this.cancelTokenSource.token;
1044
+ this.activeRequests++;
1045
+ return config;
1046
+ }, (error) => {
1047
+ handleError(error, false);
1048
+ return Promise.reject(error);
1049
+ });
1050
+ this.instance.interceptors.response.use((response) => {
1051
+ this.activeRequests--;
1052
+ return response;
1053
+ }, async (error) => {
1054
+ this.activeRequests--;
1055
+ const originalRequest = error.config;
1056
+ const isAuthError = axios.isAxiosError(error) && error.response?.status === 401;
1057
+ const isRefreshCall = originalRequest?.url === this.refreshTokenUrl;
1058
+ const isRetry = originalRequest?._retry === true;
1059
+ if (!isAuthError || isRefreshCall || isRetry) {
1060
+ handleError(error, false);
1061
+ return Promise.reject(error);
1062
+ }
1063
+ if (!originalRequest) {
1064
+ handleError(error, false);
1065
+ return Promise.reject(error);
1066
+ }
1067
+ if (this.isRefreshing) {
1068
+ return new Promise((resolve, reject) => {
1069
+ this.failedQueue.push({ resolve, reject });
1070
+ })
1071
+ .then((newToken) => {
1072
+ this.setAuthHeader(originalRequest, newToken);
1073
+ return this.instance(originalRequest);
1074
+ })
1075
+ .catch((err) => {
1076
+ return Promise.reject(err);
1077
+ });
1078
+ }
1079
+ this.isRefreshing = true;
1080
+ originalRequest._retry = true;
1081
+ try {
1082
+ await this.refreshAuth();
1083
+ const newToken = await getAuthToken(getAppKey(), "any");
1084
+ if (newToken) {
1085
+ this.processQueue(null, newToken);
1086
+ this.setAuthHeader(originalRequest, newToken);
1087
+ }
1088
+ else {
1089
+ const refreshError = new Error("New token not found after refresh.");
1090
+ this.processQueue(refreshError, null);
1091
+ throw refreshError;
1092
+ }
1093
+ this.isRefreshing = false;
1094
+ return this.instance(originalRequest);
1095
+ }
1096
+ catch (refreshError) {
1097
+ this.processQueue(refreshError, null);
1098
+ this.isRefreshing = false;
1099
+ handleError(refreshError, false);
1100
+ return Promise.reject(error);
1101
+ }
1102
+ });
1103
+ }
1104
+ getActiveRequests() {
1105
+ return this.activeRequests;
1106
+ }
1107
+ getAxiosInstance() {
1108
+ return this.instance;
1109
+ }
1110
+ cancelAllRequests() {
1111
+ this.cancelTokenSource.cancel("Operation canceled by the user.");
1112
+ this.cancelTokenSource = axios.CancelToken.source();
1113
+ }
1114
+ setHeader(key, value) {
1115
+ this.instance.defaults.headers.common[key] = value;
1116
+ }
1117
+ removeHeader(key) {
1118
+ delete this.instance.defaults.headers.common[key];
1119
+ }
1120
+ }
1121
+
1122
+ /**
1123
+ * Converts a Proxy object to a plain object.
1124
+ * @param {ProxyConstructor} proxy The Proxy object to convert.
1125
+ * @returns {Object} The plain object.
1126
+ */
1127
+ function proxyToPlainObject(proxy) {
1128
+ if (!proxy)
1129
+ return {};
1130
+ const plainObject = {};
1131
+ for (const property of Object.keys(proxy)) {
1132
+ plainObject[property] = proxy[property];
1133
+ }
1134
+ return plainObject;
1135
+ }
1136
+ /**
1137
+ * Compares two objects to check if they have the same keys.
1138
+ * @param {Object} object1 The first object to compare.
1139
+ * @param {Object} object2 The second object to compare.
1140
+ * @returns {boolean} True if the objects have the same keys, otherwise false.
1141
+ */
1142
+ function compareObject(object1, object2) {
1143
+ return Object.keys(object1).every(function (element) {
1144
+ return Object.keys(object2).includes(element);
1145
+ });
1146
+ }
1147
+ /**
1148
+ * Deeply compares two objects to check if they are equal.
1149
+ * @param {Object} object1 The first object to compare.
1150
+ * @param {Object} object2 The second object to compare.
1151
+ * @returns {boolean} True if the objects are deeply equal, otherwise false.
1152
+ */
1153
+ function deepEqual(object1, object2) {
1154
+ if (object1 === object2)
1155
+ return true;
1156
+ if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
1157
+ return false;
1158
+ }
1159
+ const keys1 = Object.keys(object1);
1160
+ const keys2 = Object.keys(object2);
1161
+ if (keys1.length !== keys2.length) {
1162
+ return false;
1163
+ }
1164
+ for (const key of keys1) {
1165
+ if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
1166
+ return false;
1167
+ }
1168
+ }
1169
+ return true;
1170
+ }
1171
+ /**
1172
+ * Deeply clones an object.
1173
+ * @param {Object} obj The object to clone.
1174
+ * @returns {Object} The cloned object.
1175
+ */
1176
+ function deepClone(obj) {
1177
+ if (obj === null || typeof obj !== 'object') {
1178
+ return obj;
1179
+ }
1180
+ if (obj instanceof Date) {
1181
+ return new Date(obj.getTime());
1182
+ }
1183
+ if (obj instanceof Array) {
1184
+ return obj.map(item => deepClone(item));
1185
+ }
1186
+ if (obj instanceof Object) {
1187
+ const copy = {};
1188
+ for (const key in obj) {
1189
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
1190
+ copy[key] = deepClone(obj[key]);
1191
+ }
1192
+ }
1193
+ return copy;
1194
+ }
1195
+ throw new Error('Unable to clone object! Its type is not supported.');
1196
+ }
1197
+ /**
1198
+ * Converts an object to a query string.
1199
+ * @param {Object} obj The object to convert.
1200
+ * @returns {string} The query string.
1201
+ */
1202
+ function objectToQueryString(obj) {
1203
+ return Object.keys(obj)
1204
+ .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
1205
+ .join('&');
1206
+ }
1207
+ // Example usage:
1208
+ // objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
1209
+ /**
1210
+ * Gets the differences between two objects.
1211
+ * @param {Object} object1 The first object.
1212
+ * @param {Object} object2 The second object.
1213
+ * @returns {Object} An object containing the differences.
1214
+ */
1215
+ function getObjectDifferences(object1, object2) {
1216
+ const differences = {};
1217
+ const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
1218
+ for (const key of keys) {
1219
+ if (object1[key] !== object2[key]) {
1220
+ differences[key] = { object1: object1[key], object2: object2[key] };
1221
+ }
1222
+ }
1223
+ return differences;
1224
+ }
1225
+ /**
1226
+ * Filters an object by a list of keys.
1227
+ * @param {Object} obj The object to filter.
1228
+ * @param {Array<string>} keys The keys to keep.
1229
+ * @returns {Object} The filtered object.
1230
+ */
1231
+ function filterObjectByKeys(obj, keys) {
1232
+ const filteredObject = {};
1233
+ for (const key of keys) {
1234
+ if (key in obj) {
1235
+ filteredObject[key] = obj[key];
1236
+ }
1237
+ }
1238
+ return filteredObject;
1239
+ }
1240
+ // Example usage:
1241
+ // filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
1242
+ /**
1243
+ * Deeply merges two objects.
1244
+ * @param {Object} target The target object to merge into.
1245
+ * @param {Object} source The source object to merge from.
1246
+ * @returns {Object} The merged object.
1247
+ */
1248
+ function deepMerge(target, source) {
1249
+ if (target === null || typeof target !== 'object' || typeof source !== 'object') {
1250
+ return target;
1251
+ }
1252
+ for (const key in source) {
1253
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1254
+ if (source[key] && typeof source[key] === 'object') {
1255
+ if (target && !target[key]) {
1256
+ Object.assign(target, { [key]: {} });
1257
+ }
1258
+ deepMerge(target[key], source[key]);
1259
+ }
1260
+ else {
1261
+ Object.assign(target, { [key]: source[key] });
1262
+ }
1263
+ }
1264
+ }
1265
+ return target;
1266
+ }
1267
+ /**
1268
+ * Checks if an object is empty.
1269
+ * @param {Object} obj The object to check.
1270
+ * @returns {boolean} True if the object is empty, otherwise false.
1271
+ */
1272
+ function isEmptyObject(obj) {
1273
+ return Object.keys(obj).length === 0;
1274
+ }
1275
+ /**
1276
+ * Safely accesses nested properties in an object.
1277
+ * @param {Object} obj The object to access.
1278
+ * @param {Array<string>} keys The array of keys representing the path.
1279
+ * @returns {any} The value at the nested path, or undefined if not found.
1280
+ */
1281
+ function safeGet(obj, keys) {
1282
+ return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
1283
+ }
1284
+ // Example usage:
1285
+ // safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
1286
+ // safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
1287
+ /**
1288
+ * Removes empty properties (null, undefined, or empty string) from an object.
1289
+ * @param {Object} obj The object to clean.
1290
+ * @returns {Object} A new object without empty properties.
1291
+ */
1292
+ function removeEmptyProperties(obj) {
1293
+ return Object.keys(obj)
1294
+ .filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
1295
+ .reduce((acc, key) => {
1296
+ acc[key] = obj[key];
1297
+ return acc;
1298
+ }, {});
1299
+ }
1300
+ // Example usage:
1301
+ // removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
1302
+ /**
1303
+ * Retrieves all keys of an object as an array.
1304
+ * @param {Object} obj The object to retrieve keys from.
1305
+ * @returns {Array<string>} The array of keys.
1306
+ */
1307
+ function getObjectKeys(obj) {
1308
+ return Object.keys(obj);
1309
+ }
1310
+ // Example usage:
1311
+ // getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
1312
+ /**
1313
+ * Checks if an object has nested properties.
1314
+ * @param {Object} obj The object to check.
1315
+ * @returns {boolean} True if there are nested properties, false otherwise.
1316
+ */
1317
+ function hasNestedProperties(obj) {
1318
+ return Object.values(obj).some(value => typeof value === 'object' && value !== null);
1319
+ }
1320
+ // Example usage:
1321
+ // hasNestedProperties({ a: 1, b: { c: 2 } }); // true
1322
+ // hasNestedProperties({ a: 1, b: 2 }); // false
1323
+ /**
1324
+ * Converts an object to FormData, handling nested objects.
1325
+ * @param {Object} obj The object to convert.
1326
+ * @param {FormData} [formData] The FormData object to append to.
1327
+ * @param {string} [parentKey] The parent key for nested objects.
1328
+ * @returns {FormData} The FormData object.
1329
+ */
1330
+ function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
1331
+ Object.entries(obj).forEach(([key, value]) => {
1332
+ const finalKey = parentKey ? `${parentKey}[${key}]` : key;
1333
+ if (value && typeof value === 'object' && !(value instanceof File)) {
1334
+ objectToFormDataEnhanced(value, formData, finalKey);
1335
+ }
1336
+ else {
1337
+ formData.append(finalKey, value);
1338
+ }
1339
+ });
1340
+ return formData;
1341
+ }
1342
+ // Example usage:
1343
+ // objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
1344
+ /**
1345
+ * Converts a JavaScript object into FormData.
1346
+ *
1347
+ * @param obj - The object to be converted.
1348
+ * @param form - An optional FormData instance to use.
1349
+ * @param namespace - An optional namespace to use for nested objects.
1350
+ * @returns The FormData instance with the object's key-value pairs.
1351
+ */
1352
+ const objectToFormData = function (obj, form, namespace) {
1353
+ const fd = form || new FormData();
1354
+ let formKey;
1355
+ for (const property in obj) {
1356
+ if (obj[property] === undefined) {
1357
+ continue;
1358
+ }
1359
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
1360
+ if (namespace) {
1361
+ formKey = `${namespace}[${property}]`;
1362
+ }
1363
+ else {
1364
+ formKey = property;
1365
+ }
1366
+ if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
1367
+ // Recursively handle nested objects
1368
+ objectToFormData(obj[property], fd, formKey);
1369
+ }
1370
+ else {
1371
+ // Convert boolean values to 1/0
1372
+ const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
1373
+ fd.append(formKey, value);
1374
+ }
1375
+ }
1376
+ }
1377
+ return fd;
1378
+ };
1379
+ /**
1380
+ * Flattens a nested object, bringing all properties to the top level.
1381
+ * @param {Object} obj The object to flatten.
1382
+ * @param {string} [parentKey] The parent key for nested properties.
1383
+ * @param {Object} [result] The resulting flattened object.
1384
+ * @returns {Object} The flattened object.
1385
+ */
1386
+ function flattenObject(obj, parentKey = '', result = {}) {
1387
+ for (const key in obj) {
1388
+ if (obj.hasOwnProperty(key)) {
1389
+ const propName = parentKey ? `${parentKey}.${key}` : key;
1390
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
1391
+ flattenObject(obj[key], propName, result);
1392
+ }
1393
+ else {
1394
+ result[propName] = obj[key];
1395
+ }
1396
+ }
1397
+ }
1398
+ return result;
1399
+ }
1400
+ // Example usage:
1401
+ // flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
1402
+
1403
+ /**
1404
+ * @typedef {object} AuthHook
1405
+ * @property {function(): Promise<number | null>} getTokenExpiry - Retrieves the expiration time of the current token in milliseconds.
1406
+ * @property {function(): Promise<void>} cleanCredentials - Clears authentication credentials from storage.
1407
+ * @property {(params?: AuthParams) => Promise<void>} logout - Logs out the user, clears credentials, and reloads the page.
1408
+ * @property {(params: AuthParams, persistence: SessionPreference, tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} login - Logs in the user and stores tokens.
1409
+ * @property {(tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} refresh - Refreshes authentication tokens.
1410
+ * @property {function(): Promise<boolean>} verifyAuth - Verifies the validity and expiration of the current authentication token.
1411
+ * @property {(preference: SessionPreference) => void} setSessionPersistencePreference - Sets the user's preferred storage for authentication data.
1412
+ * @property {function(): SessionPreference} getSessionPersistence - Retrieves the user's current preferred storage for authentication data.
1413
+ */
1414
+ /**
1415
+ * Custom hook for authentication logic, including login, logout, token management, and session preference.
1416
+ *
1417
+ * @param {string} secretKey - The secret key used for token encryption/decryption.
1418
+ * @returns {AuthHook} An object containing authentication functions.
1419
+ */
1420
+ function useAuth(secretKey = getAppKey()) {
1421
+ const axiosInstance = getAxiosInstance();
1422
+ const endpoints = getEndpointsConfig();
1423
+ /**
1424
+ * Logs out the user by making a POST request to the logout endpoint,
1425
+ * cleaning all stored credentials, and reloading the page.
1426
+ * The session persistence preference is NOT reset here; it persists across logouts.
1427
+ *
1428
+ * @param {AuthParams} [params={}] - Optional parameters to send with the logout request.
1429
+ * @returns {Promise<void>}
1430
+ */
1431
+ const logout = async (params = {}) => {
1432
+ try {
1433
+ await axiosInstance.post(endpoints.LOGOUT, params);
1434
+ }
1435
+ catch (error) {
1436
+ handleError(error, false);
1437
+ }
1438
+ finally {
1439
+ await cleanCredentials(await getSessionPersistence());
1440
+ window.location.reload();
1441
+ }
1442
+ };
1443
+ /**
1444
+ * Authenticates the user by making a POST request to the login endpoint,
1445
+ * stores the received access and refresh tokens, and sets the session persistence preference.
1446
+ *
1447
+ * @param {AuthParams} params - The authentication parameters (e.g., username, password).
1448
+ * @param {SessionPreference} persistence - The storage preference: 'local' for localStorage, 'session' for sessionStorage.
1449
+ * @param {AuthTokenPaths} [tokenPaths] - Configuración opcional para las rutas (en notación de punto) donde se encuentran los tokens en la respuesta de la API.
1450
+ * @returns {Promise<AuthResponse>} La respuesta de autenticación que contiene los tokens e información del usuario.
1451
+ * @throws {Error} Si la solicitud de login falla, o si los tokens de acceso/refresco no se encuentran o no son válidos en la respuesta.
1452
+ */
1453
+ const login = async (params, persistence, tokenPaths) => {
1454
+ try {
1455
+ const { data } = await axiosInstance.post(endpoints.LOGIN, params);
1456
+ const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
1457
+ const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
1458
+ const accessTokenPathArray = accessTokenPath.split(".");
1459
+ const refreshTokenPathArray = refreshTokenPath.split(".");
1460
+ if (!data) {
1461
+ throw new Error("LOGIN_ERROR: No data received from login endpoint.");
1462
+ }
1463
+ const accessToken = safeGet(data, accessTokenPathArray);
1464
+ const refreshToken = safeGet(data, refreshTokenPathArray);
1465
+ if (!accessToken || typeof accessToken !== "string") {
1466
+ throw new Error(`LOGIN_ERROR: Access token not found or invalid at path '${accessTokenPath}' in response.`);
1467
+ }
1468
+ if (!refreshToken || typeof refreshToken !== "string") {
1469
+ throw new Error(`LOGIN_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in response.`);
1470
+ }
1471
+ configSession({
1472
+ persistencePreference: persistence,
1473
+ });
1474
+ await storeAuthToken(accessToken, secretKey, persistence);
1475
+ await storeAuthRefreshToken(refreshToken, secretKey, persistence);
1476
+ return data;
1477
+ }
1478
+ catch (error) {
1479
+ handleError(error, false);
1480
+ throw error;
1481
+ }
1482
+ };
1483
+ /**
1484
+ * Refreshes the authentication tokens using the stored refresh token.
1485
+ * This function can also accept optional token paths if the refresh endpoint
1486
+ * returns tokens with a different structure than the default login.
1487
+ * If no refresh token is found, it throws an error and initiates a logout.
1488
+ *
1489
+ * @param {AuthTokenPaths} [tokenPaths] - Configuración opcional para las rutas (en notación de punto) de los tokens de acceso y refresco en la respuesta del endpoint de refresco.
1490
+ * @returns {Promise<AuthResponse>} The new authentication response with refreshed tokens.
1491
+ * @throws {Error} If the refresh token is missing or the refresh request fails.
1492
+ */
1493
+ const refresh = async (tokenPaths) => {
1494
+ try {
1495
+ const refreshTokenFromStorage = await getAuthRefreshToken(secretKey, 'any');
1496
+ if (!refreshTokenFromStorage) {
1497
+ throw new Error("TOKEN_MISSING: No refresh token found in storage.");
1498
+ }
1499
+ const { data } = await axiosInstance.post(endpoints.REFRESH, { refresh_token: refreshTokenFromStorage });
1500
+ const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
1501
+ const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
1502
+ const accessTokenPathArray = accessTokenPath.split(".");
1503
+ const refreshTokenPathArray = refreshTokenPath.split(".");
1504
+ if (!data) {
1505
+ throw new Error("REFRESH_ERROR: No data received from refresh endpoint.");
1506
+ }
1507
+ const accessTokenAfterRefresh = safeGet(data, accessTokenPathArray);
1508
+ const refreshTokenAfterRefresh = safeGet(data, refreshTokenPathArray);
1509
+ if (!accessTokenAfterRefresh ||
1510
+ typeof accessTokenAfterRefresh !== "string") {
1511
+ throw new Error(`REFRESH_ERROR: Access token not found or invalid at path '${accessTokenPath}' in refresh response.`);
1512
+ }
1513
+ if (!refreshTokenAfterRefresh ||
1514
+ typeof refreshTokenAfterRefresh !== "string") {
1515
+ throw new Error(`REFRESH_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in refresh response.`);
1516
+ }
1517
+ await storeAuthToken(accessTokenAfterRefresh, secretKey, await getSessionPersistence());
1518
+ await storeAuthRefreshToken(refreshTokenAfterRefresh, secretKey, await getSessionPersistence());
1519
+ return data;
1520
+ }
1521
+ catch (error) {
1522
+ handleError(error, false);
1523
+ await logout();
1524
+ throw error;
1525
+ }
1526
+ };
1527
+ return {
1528
+ logout,
1529
+ login,
1530
+ refresh,
1531
+ };
1532
+ }
1533
+
1534
+ let axiosInstance;
1535
+ const auth = useAuth();
1536
+ const refreshAndForget = async () => {
1537
+ await auth.refresh();
1538
+ };
1539
+ const configAxios = (config) => {
1540
+ axiosInstance = new AxiosService({
1541
+ baseURL: config.baseURL,
1542
+ }, refreshAndForget);
1543
+ };
1544
+ const getAxiosInstance = () => {
1545
+ if (!axiosInstance) {
1546
+ throw new Error("Axios instance not configured. Call configAxios first.");
1547
+ }
1548
+ return axiosInstance.getAxiosInstance();
1549
+ };
1550
+ const createCustomAxiosInstance = (config) => {
1551
+ return new AxiosService({
1552
+ baseURL: config.baseURL,
1553
+ headers: config.headers,
1554
+ }, refreshAndForget).getAxiosInstance();
1555
+ };
1556
+
1557
+ /**
1558
+ * Creates and downloads a file from Blob data.
1559
+ * @param {Blob} blob The Blob containing the file data.
1560
+ * @param {string} fileName The name of the file to create.
1561
+ */
1562
+ function downloadFile(blob, fileName) {
1563
+ const link = document.createElement('a');
1564
+ const url = URL.createObjectURL(blob);
1565
+ link.setAttribute('href', url);
1566
+ link.setAttribute('download', fileName);
1567
+ // Append link to the body and trigger a click
1568
+ document.body.appendChild(link);
1569
+ link.click();
1570
+ // Clean up
1571
+ document.body.removeChild(link);
1572
+ URL.revokeObjectURL(url);
1573
+ }
1574
+ /**
1575
+ * Exports data to a CSV file.
1576
+ * @param {string[]} headers The headers for the CSV.
1577
+ * @param {any[][]} data The data to export, as an array of arrays.
1578
+ * @param {string} fileName The name of the file to create.
1579
+ */
1580
+ function exportToCSV(headers, data, fileName) {
1581
+ const csvRows = [];
1582
+ // Add the headers
1583
+ csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
1584
+ // Add the data rows
1585
+ for (const row of data) {
1586
+ csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
1587
+ }
1588
+ // Create a Blob with CSV data
1589
+ const csvContent = csvRows.join('\n');
1590
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1078
1591
  // Use the download utility function
1079
1592
  downloadFile(blob, fileName);
1080
1593
  }
@@ -1879,417 +2392,136 @@ function debounceLeadingTrailing(func, wait) {
1879
2392
  };
1880
2393
  }
1881
2394
  /**
1882
- * Creates a debounced version of a function.
1883
- * @param {Function} func The function to debounce.
1884
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1885
- * @returns {Function} The debounced function.
1886
- */
1887
- function debounce(func, wait) {
1888
- let timeoutReject = null;
1889
- return function (...args) {
1890
- if (timeoutReject) {
1891
- timeoutReject(new Error('replaced'));
1892
- timeoutReject = null;
1893
- }
1894
- const { start } = useTimeoutFn(() => {
1895
- func.apply(this, args);
1896
- }, wait);
1897
- timeoutReject = () => { };
1898
- start();
1899
- };
1900
- }
1901
- /**
1902
- * Creates a throttled version of a function.
1903
- * @param {Function} func The function to throttle.
1904
- * @param {number} limit The number of milliseconds to wait between function calls.
1905
- * @returns {Function} The throttled function.
1906
- */
1907
- function throttle(func, limit) {
1908
- let lastCall = 0;
1909
- return function (...args) {
1910
- const now = Date.now();
1911
- if (now - lastCall >= limit) {
1912
- lastCall = now;
1913
- func.apply(this, args);
1914
- }
1915
- };
1916
- }
1917
-
1918
- /**
1919
- * Converts a FormData object to a plain JavaScript object.
1920
- * @param {FormData} formData The FormData object to convert.
1921
- * @returns {Record<string, any>} The plain JavaScript object.
1922
- */
1923
- function formDataToObject(formData) {
1924
- const obj = {};
1925
- formData.forEach((value, key) => {
1926
- // Handle multiple values for the same key
1927
- if (obj[key]) {
1928
- if (!Array.isArray(obj[key])) {
1929
- obj[key] = [obj[key]];
1930
- }
1931
- obj[key].push(value);
1932
- }
1933
- else {
1934
- obj[key] = value;
1935
- }
1936
- });
1937
- return obj;
1938
- }
1939
- /**
1940
- * Reads a file as text.
1941
- * @param {File} file The file to read.
1942
- * @returns {Promise<string>} A promise that resolves with the file content.
1943
- */
1944
- function readFileAsText(file) {
1945
- return new Promise((resolve, reject) => {
1946
- const reader = new FileReader();
1947
- reader.onload = () => resolve(reader.result);
1948
- reader.onerror = reject;
1949
- reader.readAsText(file);
1950
- });
1951
- }
1952
- /**
1953
- * Reads a file as a Data URL.
1954
- * @param {File} file The file to read.
1955
- * @returns {Promise<string>} A promise that resolves with the Data URL.
1956
- */
1957
- function readFileAsDataURL(file) {
1958
- return new Promise((resolve, reject) => {
1959
- const reader = new FileReader();
1960
- reader.onload = () => resolve(reader.result);
1961
- reader.onerror = reject;
1962
- reader.readAsDataURL(file);
1963
- });
1964
- }
1965
- /**
1966
- * Creates a Blob from a string.
1967
- * @param {string} content The string content for the Blob.
1968
- * @param {string} [type='text/plain'] The MIME type of the Blob.
1969
- * @returns {Blob} The Blob object.
1970
- */
1971
- function stringToBlob(content, type = 'text/plain') {
1972
- return new Blob([content], { type });
1973
- }
1974
- /**
1975
- * Creates a Blob from an ArrayBuffer.
1976
- * @param {ArrayBuffer} buffer The ArrayBuffer to convert.
1977
- * @param {string} [type='application/octet-stream'] The MIME type of the Blob.
1978
- * @returns {Blob} The Blob object.
1979
- */
1980
- function bufferToBlob(buffer, type = 'application/octet-stream') {
1981
- return new Blob([buffer], { type });
1982
- }
1983
- /**
1984
- * Creates and downloads a file from Blob data.
1985
- * @param {Blob} blob The Blob containing the file data.
1986
- * @param {string} fileName The name of the file to create.
1987
- */
1988
- function downloadBlob(blob, fileName) {
1989
- const link = document.createElement('a');
1990
- const url = URL.createObjectURL(blob);
1991
- link.setAttribute('href', url);
1992
- link.setAttribute('download', fileName);
1993
- // Append link to the body and trigger a click
1994
- document.body.appendChild(link);
1995
- link.click();
1996
- // Clean up
1997
- document.body.removeChild(link);
1998
- URL.revokeObjectURL(url);
1999
- }
2000
- /**
2001
- * Creates a FormData object containing a Blob.
2002
- * @param {Blob} blob The Blob to include in the FormData.
2003
- * @param {string} name The name of the form field.
2004
- * @param {string} [fileName='file'] The file name for the Blob.
2005
- * @returns {FormData} The FormData object.
2006
- */
2007
- function blobToFormData(blob, name, fileName = 'file') {
2008
- const formData = new FormData();
2009
- formData.append(name, blob, fileName);
2010
- return formData;
2011
- }
2012
-
2013
- /**
2014
- * Converts a Proxy object to a plain object.
2015
- * @param {ProxyConstructor} proxy The Proxy object to convert.
2016
- * @returns {Object} The plain object.
2017
- */
2018
- function proxyToPlainObject(proxy) {
2019
- if (!proxy)
2020
- return {};
2021
- const plainObject = {};
2022
- for (const property of Object.keys(proxy)) {
2023
- plainObject[property] = proxy[property];
2024
- }
2025
- return plainObject;
2026
- }
2027
- /**
2028
- * Compares two objects to check if they have the same keys.
2029
- * @param {Object} object1 The first object to compare.
2030
- * @param {Object} object2 The second object to compare.
2031
- * @returns {boolean} True if the objects have the same keys, otherwise false.
2032
- */
2033
- function compareObject(object1, object2) {
2034
- return Object.keys(object1).every(function (element) {
2035
- return Object.keys(object2).includes(element);
2036
- });
2037
- }
2038
- /**
2039
- * Deeply compares two objects to check if they are equal.
2040
- * @param {Object} object1 The first object to compare.
2041
- * @param {Object} object2 The second object to compare.
2042
- * @returns {boolean} True if the objects are deeply equal, otherwise false.
2043
- */
2044
- function deepEqual(object1, object2) {
2045
- if (object1 === object2)
2046
- return true;
2047
- if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
2048
- return false;
2049
- }
2050
- const keys1 = Object.keys(object1);
2051
- const keys2 = Object.keys(object2);
2052
- if (keys1.length !== keys2.length) {
2053
- return false;
2054
- }
2055
- for (const key of keys1) {
2056
- if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
2057
- return false;
2058
- }
2059
- }
2060
- return true;
2061
- }
2062
- /**
2063
- * Deeply clones an object.
2064
- * @param {Object} obj The object to clone.
2065
- * @returns {Object} The cloned object.
2066
- */
2067
- function deepClone(obj) {
2068
- if (obj === null || typeof obj !== 'object') {
2069
- return obj;
2070
- }
2071
- if (obj instanceof Date) {
2072
- return new Date(obj.getTime());
2073
- }
2074
- if (obj instanceof Array) {
2075
- return obj.map(item => deepClone(item));
2076
- }
2077
- if (obj instanceof Object) {
2078
- const copy = {};
2079
- for (const key in obj) {
2080
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
2081
- copy[key] = deepClone(obj[key]);
2082
- }
2083
- }
2084
- return copy;
2085
- }
2086
- throw new Error('Unable to clone object! Its type is not supported.');
2087
- }
2088
- /**
2089
- * Converts an object to a query string.
2090
- * @param {Object} obj The object to convert.
2091
- * @returns {string} The query string.
2092
- */
2093
- function objectToQueryString(obj) {
2094
- return Object.keys(obj)
2095
- .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
2096
- .join('&');
2097
- }
2098
- // Example usage:
2099
- // objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
2100
- /**
2101
- * Gets the differences between two objects.
2102
- * @param {Object} object1 The first object.
2103
- * @param {Object} object2 The second object.
2104
- * @returns {Object} An object containing the differences.
2395
+ * Creates a debounced version of a function.
2396
+ * @param {Function} func The function to debounce.
2397
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
2398
+ * @returns {Function} The debounced function.
2105
2399
  */
2106
- function getObjectDifferences(object1, object2) {
2107
- const differences = {};
2108
- const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
2109
- for (const key of keys) {
2110
- if (object1[key] !== object2[key]) {
2111
- differences[key] = { object1: object1[key], object2: object2[key] };
2400
+ function debounce(func, wait) {
2401
+ let timeoutReject = null;
2402
+ return function (...args) {
2403
+ if (timeoutReject) {
2404
+ timeoutReject(new Error('replaced'));
2405
+ timeoutReject = null;
2112
2406
  }
2113
- }
2114
- return differences;
2407
+ const { start } = useTimeoutFn(() => {
2408
+ func.apply(this, args);
2409
+ }, wait);
2410
+ timeoutReject = () => { };
2411
+ start();
2412
+ };
2115
2413
  }
2116
2414
  /**
2117
- * Filters an object by a list of keys.
2118
- * @param {Object} obj The object to filter.
2119
- * @param {Array<string>} keys The keys to keep.
2120
- * @returns {Object} The filtered object.
2415
+ * Creates a throttled version of a function.
2416
+ * @param {Function} func The function to throttle.
2417
+ * @param {number} limit The number of milliseconds to wait between function calls.
2418
+ * @returns {Function} The throttled function.
2121
2419
  */
2122
- function filterObjectByKeys(obj, keys) {
2123
- const filteredObject = {};
2124
- for (const key of keys) {
2125
- if (key in obj) {
2126
- filteredObject[key] = obj[key];
2420
+ function throttle(func, limit) {
2421
+ let lastCall = 0;
2422
+ return function (...args) {
2423
+ const now = Date.now();
2424
+ if (now - lastCall >= limit) {
2425
+ lastCall = now;
2426
+ func.apply(this, args);
2127
2427
  }
2128
- }
2129
- return filteredObject;
2428
+ };
2130
2429
  }
2131
- // Example usage:
2132
- // filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
2430
+
2133
2431
  /**
2134
- * Deeply merges two objects.
2135
- * @param {Object} target The target object to merge into.
2136
- * @param {Object} source The source object to merge from.
2137
- * @returns {Object} The merged object.
2432
+ * Converts a FormData object to a plain JavaScript object.
2433
+ * @param {FormData} formData The FormData object to convert.
2434
+ * @returns {Record<string, any>} The plain JavaScript object.
2138
2435
  */
2139
- function deepMerge(target, source) {
2140
- if (target === null || typeof target !== 'object' || typeof source !== 'object') {
2141
- return target;
2142
- }
2143
- for (const key in source) {
2144
- if (Object.prototype.hasOwnProperty.call(source, key)) {
2145
- if (source[key] && typeof source[key] === 'object') {
2146
- if (target && !target[key]) {
2147
- Object.assign(target, { [key]: {} });
2148
- }
2149
- deepMerge(target[key], source[key]);
2150
- }
2151
- else {
2152
- Object.assign(target, { [key]: source[key] });
2436
+ function formDataToObject(formData) {
2437
+ const obj = {};
2438
+ formData.forEach((value, key) => {
2439
+ // Handle multiple values for the same key
2440
+ if (obj[key]) {
2441
+ if (!Array.isArray(obj[key])) {
2442
+ obj[key] = [obj[key]];
2153
2443
  }
2444
+ obj[key].push(value);
2154
2445
  }
2155
- }
2156
- return target;
2157
- }
2158
- /**
2159
- * Checks if an object is empty.
2160
- * @param {Object} obj The object to check.
2161
- * @returns {boolean} True if the object is empty, otherwise false.
2162
- */
2163
- function isEmptyObject(obj) {
2164
- return Object.keys(obj).length === 0;
2165
- }
2166
- /**
2167
- * Safely accesses nested properties in an object.
2168
- * @param {Object} obj The object to access.
2169
- * @param {Array<string>} keys The array of keys representing the path.
2170
- * @returns {any} The value at the nested path, or undefined if not found.
2171
- */
2172
- function safeGet(obj, keys) {
2173
- return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
2446
+ else {
2447
+ obj[key] = value;
2448
+ }
2449
+ });
2450
+ return obj;
2174
2451
  }
2175
- // Example usage:
2176
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
2177
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
2178
2452
  /**
2179
- * Removes empty properties (null, undefined, or empty string) from an object.
2180
- * @param {Object} obj The object to clean.
2181
- * @returns {Object} A new object without empty properties.
2453
+ * Reads a file as text.
2454
+ * @param {File} file The file to read.
2455
+ * @returns {Promise<string>} A promise that resolves with the file content.
2182
2456
  */
2183
- function removeEmptyProperties(obj) {
2184
- return Object.keys(obj)
2185
- .filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
2186
- .reduce((acc, key) => {
2187
- acc[key] = obj[key];
2188
- return acc;
2189
- }, {});
2457
+ function readFileAsText(file) {
2458
+ return new Promise((resolve, reject) => {
2459
+ const reader = new FileReader();
2460
+ reader.onload = () => resolve(reader.result);
2461
+ reader.onerror = reject;
2462
+ reader.readAsText(file);
2463
+ });
2190
2464
  }
2191
- // Example usage:
2192
- // removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
2193
2465
  /**
2194
- * Retrieves all keys of an object as an array.
2195
- * @param {Object} obj The object to retrieve keys from.
2196
- * @returns {Array<string>} The array of keys.
2466
+ * Reads a file as a Data URL.
2467
+ * @param {File} file The file to read.
2468
+ * @returns {Promise<string>} A promise that resolves with the Data URL.
2197
2469
  */
2198
- function getObjectKeys(obj) {
2199
- return Object.keys(obj);
2470
+ function readFileAsDataURL(file) {
2471
+ return new Promise((resolve, reject) => {
2472
+ const reader = new FileReader();
2473
+ reader.onload = () => resolve(reader.result);
2474
+ reader.onerror = reject;
2475
+ reader.readAsDataURL(file);
2476
+ });
2200
2477
  }
2201
- // Example usage:
2202
- // getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
2203
2478
  /**
2204
- * Checks if an object has nested properties.
2205
- * @param {Object} obj The object to check.
2206
- * @returns {boolean} True if there are nested properties, false otherwise.
2479
+ * Creates a Blob from a string.
2480
+ * @param {string} content The string content for the Blob.
2481
+ * @param {string} [type='text/plain'] The MIME type of the Blob.
2482
+ * @returns {Blob} The Blob object.
2207
2483
  */
2208
- function hasNestedProperties(obj) {
2209
- return Object.values(obj).some(value => typeof value === 'object' && value !== null);
2484
+ function stringToBlob(content, type = 'text/plain') {
2485
+ return new Blob([content], { type });
2210
2486
  }
2211
- // Example usage:
2212
- // hasNestedProperties({ a: 1, b: { c: 2 } }); // true
2213
- // hasNestedProperties({ a: 1, b: 2 }); // false
2214
2487
  /**
2215
- * Converts an object to FormData, handling nested objects.
2216
- * @param {Object} obj The object to convert.
2217
- * @param {FormData} [formData] The FormData object to append to.
2218
- * @param {string} [parentKey] The parent key for nested objects.
2219
- * @returns {FormData} The FormData object.
2488
+ * Creates a Blob from an ArrayBuffer.
2489
+ * @param {ArrayBuffer} buffer The ArrayBuffer to convert.
2490
+ * @param {string} [type='application/octet-stream'] The MIME type of the Blob.
2491
+ * @returns {Blob} The Blob object.
2220
2492
  */
2221
- function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
2222
- Object.entries(obj).forEach(([key, value]) => {
2223
- const finalKey = parentKey ? `${parentKey}[${key}]` : key;
2224
- if (value && typeof value === 'object' && !(value instanceof File)) {
2225
- objectToFormDataEnhanced(value, formData, finalKey);
2226
- }
2227
- else {
2228
- formData.append(finalKey, value);
2229
- }
2230
- });
2231
- return formData;
2493
+ function bufferToBlob(buffer, type = 'application/octet-stream') {
2494
+ return new Blob([buffer], { type });
2232
2495
  }
2233
- // Example usage:
2234
- // objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
2235
- /**
2236
- * Converts a JavaScript object into FormData.
2237
- *
2238
- * @param obj - The object to be converted.
2239
- * @param form - An optional FormData instance to use.
2240
- * @param namespace - An optional namespace to use for nested objects.
2241
- * @returns The FormData instance with the object's key-value pairs.
2242
- */
2243
- const objectToFormData = function (obj, form, namespace) {
2244
- const fd = form || new FormData();
2245
- let formKey;
2246
- for (const property in obj) {
2247
- if (obj[property] === undefined) {
2248
- continue;
2249
- }
2250
- if (Object.prototype.hasOwnProperty.call(obj, property)) {
2251
- if (namespace) {
2252
- formKey = `${namespace}[${property}]`;
2253
- }
2254
- else {
2255
- formKey = property;
2256
- }
2257
- if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
2258
- // Recursively handle nested objects
2259
- objectToFormData(obj[property], fd, formKey);
2260
- }
2261
- else {
2262
- // Convert boolean values to 1/0
2263
- const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
2264
- fd.append(formKey, value);
2265
- }
2266
- }
2267
- }
2268
- return fd;
2269
- };
2270
2496
  /**
2271
- * Flattens a nested object, bringing all properties to the top level.
2272
- * @param {Object} obj The object to flatten.
2273
- * @param {string} [parentKey] The parent key for nested properties.
2274
- * @param {Object} [result] The resulting flattened object.
2275
- * @returns {Object} The flattened object.
2497
+ * Creates and downloads a file from Blob data.
2498
+ * @param {Blob} blob The Blob containing the file data.
2499
+ * @param {string} fileName The name of the file to create.
2276
2500
  */
2277
- function flattenObject(obj, parentKey = '', result = {}) {
2278
- for (const key in obj) {
2279
- if (obj.hasOwnProperty(key)) {
2280
- const propName = parentKey ? `${parentKey}.${key}` : key;
2281
- if (typeof obj[key] === 'object' && obj[key] !== null) {
2282
- flattenObject(obj[key], propName, result);
2283
- }
2284
- else {
2285
- result[propName] = obj[key];
2286
- }
2287
- }
2288
- }
2289
- return result;
2501
+ function downloadBlob(blob, fileName) {
2502
+ const link = document.createElement('a');
2503
+ const url = URL.createObjectURL(blob);
2504
+ link.setAttribute('href', url);
2505
+ link.setAttribute('download', fileName);
2506
+ // Append link to the body and trigger a click
2507
+ document.body.appendChild(link);
2508
+ link.click();
2509
+ // Clean up
2510
+ document.body.removeChild(link);
2511
+ URL.revokeObjectURL(url);
2512
+ }
2513
+ /**
2514
+ * Creates a FormData object containing a Blob.
2515
+ * @param {Blob} blob The Blob to include in the FormData.
2516
+ * @param {string} name The name of the form field.
2517
+ * @param {string} [fileName='file'] The file name for the Blob.
2518
+ * @returns {FormData} The FormData object.
2519
+ */
2520
+ function blobToFormData(blob, name, fileName = 'file') {
2521
+ const formData = new FormData();
2522
+ formData.append(name, blob, fileName);
2523
+ return formData;
2290
2524
  }
2291
- // Example usage:
2292
- // flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
2293
2525
 
2294
2526
  /**
2295
2527
  * Capitalizes the first character of a string.
@@ -2703,118 +2935,6 @@ function isValidHexNumber(hex) {
2703
2935
  // console.log(isValidHexNumber('1A3F')); // true
2704
2936
  // console.log(isValidHexNumber('GHIJ')); // false
2705
2937
 
2706
- /**
2707
- * Clears all stored authentication data (access and refresh tokens)
2708
- * from either sessionStorage, localStorage, or both based on the provided location preference.
2709
- *
2710
- * @param {LocationPreference} location - The storage preference ('local' for localStorage, 'session' for sessionStorage, 'any' for both).
2711
- * @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
2712
- */
2713
- const cleanCredentials = async (location) => {
2714
- const tokensConfig = getTokenConfig();
2715
- Object.keys(tokensConfig).forEach((key) => {
2716
- const itemKey = tokensConfig[key];
2717
- if (location === "local" || location === "any") {
2718
- localStorage.removeItem(itemKey);
2719
- }
2720
- if (location === "session" || location === "any") {
2721
- sessionStorage.removeItem(itemKey);
2722
- }
2723
- });
2724
- };
2725
- /**
2726
- * Retrieves the authentication token (access token) from storage, decrypting it
2727
- * using the provided secret key and based on the specified session preference.
2728
- *
2729
- * @param {string} secretKey - The secret key used for decryption.
2730
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
2731
- * @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
2732
- */
2733
- const getAuthToken = async (secretKey, location) => {
2734
- const tokensConfig = getTokenConfig();
2735
- return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
2736
- };
2737
- /**
2738
- * Retrieves the authentication refresh token from storage, decrypting it
2739
- * using the provided secret key and based on the specified session preference.
2740
- *
2741
- * @param {string} secretKey - The secret key used for decryption.
2742
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
2743
- * @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
2744
- */
2745
- const getAuthRefreshToken = async (secretKey, location) => {
2746
- const tokensConfig = getTokenConfig();
2747
- return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
2748
- };
2749
- /**
2750
- * Stores the authentication token (access token) in storage after encrypting it,
2751
- * based on the specified session preference.
2752
- *
2753
- * @param {string} token - The access token to store.
2754
- * @param {string} secretKey - The secret key used for encryption.
2755
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
2756
- * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
2757
- */
2758
- const storeAuthToken = async (token, secretKey, location) => {
2759
- const tokensConfig = getTokenConfig();
2760
- await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
2761
- };
2762
- /**
2763
- * Stores the authentication refresh token in storage after encrypting it,
2764
- * based on the specified session preference.
2765
- *
2766
- * @param {string} token - The refresh token to store.
2767
- * @param {string} secretKey - The secret key used for encryption.
2768
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
2769
- * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
2770
- */
2771
- const storeAuthRefreshToken = async (token, secretKey, location) => {
2772
- const tokensConfig = getTokenConfig();
2773
- await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
2774
- };
2775
- /**
2776
- * Verifies the validity and expiration of the current authentication token.
2777
- * If the token is missing, invalid, or expired, appropriate errors are thrown and credentials are cleaned.
2778
- *
2779
- * @returns {Promise<boolean>} True if the token is valid and unexpired.
2780
- * @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
2781
- * "TOKEN_INVALID" if the token format is invalid.
2782
- */
2783
- const verifyAuth = async () => {
2784
- const sessionPersistence = 'any';
2785
- const handleAuthError = async (message, shouldClean = true) => {
2786
- handleError(message, false);
2787
- if (shouldClean) {
2788
- await cleanCredentials(sessionPersistence);
2789
- }
2790
- return false;
2791
- };
2792
- try {
2793
- const token = await getAuthToken(getAppKey(), sessionPersistence);
2794
- if (!token) {
2795
- return await handleAuthError("TOKEN_MISSING: No valid token found");
2796
- }
2797
- let decoded;
2798
- try {
2799
- decoded = jwtDecode(token);
2800
- }
2801
- catch (decodeError) {
2802
- return await handleAuthError("TOKEN_INVALID: Invalid token format");
2803
- }
2804
- const currentTime = Date.now() / 1000;
2805
- if (typeof decoded.exp !== "number") {
2806
- return await handleAuthError("TOKEN_INVALID: Invalid expiration format");
2807
- }
2808
- if (decoded.exp <= currentTime) {
2809
- return await handleAuthError("TOKEN_EXPIRED: Token is expired");
2810
- }
2811
- return true;
2812
- }
2813
- catch (error) {
2814
- return await handleAuthError("AUTH_ERROR: An unexpected error occurred", true);
2815
- }
2816
- };
2817
-
2818
2938
  class RestStd {
2819
2939
  static resource;
2820
2940
  static isFormData = false;
@@ -3366,137 +3486,6 @@ function useSorter(items, criteriaList, selectedCriteria) {
3366
3486
  }).value;
3367
3487
  }
3368
3488
 
3369
- /**
3370
- * @typedef {object} AuthHook
3371
- * @property {function(): Promise<number | null>} getTokenExpiry - Retrieves the expiration time of the current token in milliseconds.
3372
- * @property {function(): Promise<void>} cleanCredentials - Clears authentication credentials from storage.
3373
- * @property {(params?: AuthParams) => Promise<void>} logout - Logs out the user, clears credentials, and reloads the page.
3374
- * @property {(params: AuthParams, persistence: SessionPreference, tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} login - Logs in the user and stores tokens.
3375
- * @property {(tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} refresh - Refreshes authentication tokens.
3376
- * @property {function(): Promise<boolean>} verifyAuth - Verifies the validity and expiration of the current authentication token.
3377
- * @property {(preference: SessionPreference) => void} setSessionPersistencePreference - Sets the user's preferred storage for authentication data.
3378
- * @property {function(): SessionPreference} getSessionPersistence - Retrieves the user's current preferred storage for authentication data.
3379
- */
3380
- /**
3381
- * Custom hook for authentication logic, including login, logout, token management, and session preference.
3382
- *
3383
- * @param {string} secretKey - The secret key used for token encryption/decryption.
3384
- * @returns {AuthHook} An object containing authentication functions.
3385
- */
3386
- function useAuth(secretKey = getAppKey()) {
3387
- const axiosInstance = getAxiosInstance();
3388
- const endpoints = getEndpointsConfig();
3389
- /**
3390
- * Logs out the user by making a POST request to the logout endpoint,
3391
- * cleaning all stored credentials, and reloading the page.
3392
- * The session persistence preference is NOT reset here; it persists across logouts.
3393
- *
3394
- * @param {AuthParams} [params={}] - Optional parameters to send with the logout request.
3395
- * @returns {Promise<void>}
3396
- */
3397
- const logout = async (params = {}) => {
3398
- try {
3399
- await axiosInstance.post(endpoints.LOGOUT, params);
3400
- }
3401
- catch (error) {
3402
- handleError(error, false);
3403
- }
3404
- finally {
3405
- await cleanCredentials(await getSessionPersistence());
3406
- window.location.reload();
3407
- }
3408
- };
3409
- /**
3410
- * Authenticates the user by making a POST request to the login endpoint,
3411
- * stores the received access and refresh tokens, and sets the session persistence preference.
3412
- *
3413
- * @param {AuthParams} params - The authentication parameters (e.g., username, password).
3414
- * @param {SessionPreference} persistence - The storage preference: 'local' for localStorage, 'session' for sessionStorage.
3415
- * @param {AuthTokenPaths} [tokenPaths] - Configuración opcional para las rutas (en notación de punto) donde se encuentran los tokens en la respuesta de la API.
3416
- * @returns {Promise<AuthResponse>} La respuesta de autenticación que contiene los tokens e información del usuario.
3417
- * @throws {Error} Si la solicitud de login falla, o si los tokens de acceso/refresco no se encuentran o no son válidos en la respuesta.
3418
- */
3419
- const login = async (params, persistence, tokenPaths) => {
3420
- try {
3421
- const { data } = await axiosInstance.post(endpoints.LOGIN, params);
3422
- const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
3423
- const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
3424
- const accessTokenPathArray = accessTokenPath.split(".");
3425
- const refreshTokenPathArray = refreshTokenPath.split(".");
3426
- if (!data) {
3427
- throw new Error("LOGIN_ERROR: No data received from login endpoint.");
3428
- }
3429
- const accessToken = safeGet(data, accessTokenPathArray);
3430
- const refreshToken = safeGet(data, refreshTokenPathArray);
3431
- if (!accessToken || typeof accessToken !== "string") {
3432
- throw new Error(`LOGIN_ERROR: Access token not found or invalid at path '${accessTokenPath}' in response.`);
3433
- }
3434
- if (!refreshToken || typeof refreshToken !== "string") {
3435
- throw new Error(`LOGIN_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in response.`);
3436
- }
3437
- configSession({
3438
- persistencePreference: persistence,
3439
- });
3440
- await storeAuthToken(accessToken, secretKey, persistence);
3441
- await storeAuthRefreshToken(refreshToken, secretKey, persistence);
3442
- return data;
3443
- }
3444
- catch (error) {
3445
- handleError(error, false);
3446
- throw error;
3447
- }
3448
- };
3449
- /**
3450
- * Refreshes the authentication tokens using the stored refresh token.
3451
- * This function can also accept optional token paths if the refresh endpoint
3452
- * returns tokens with a different structure than the default login.
3453
- * If no refresh token is found, it throws an error and initiates a logout.
3454
- *
3455
- * @param {AuthTokenPaths} [tokenPaths] - Configuración opcional para las rutas (en notación de punto) de los tokens de acceso y refresco en la respuesta del endpoint de refresco.
3456
- * @returns {Promise<AuthResponse>} The new authentication response with refreshed tokens.
3457
- * @throws {Error} If the refresh token is missing or the refresh request fails.
3458
- */
3459
- const refresh = async (tokenPaths) => {
3460
- try {
3461
- const refreshTokenFromStorage = await getAuthRefreshToken(secretKey, await getSessionPersistence());
3462
- if (!refreshTokenFromStorage) {
3463
- throw new Error("TOKEN_MISSING: No refresh token found in storage.");
3464
- }
3465
- const { data } = await axiosInstance.post(endpoints.REFRESH, { refresh_token: refreshTokenFromStorage });
3466
- const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
3467
- const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
3468
- const accessTokenPathArray = accessTokenPath.split(".");
3469
- const refreshTokenPathArray = refreshTokenPath.split(".");
3470
- if (!data) {
3471
- throw new Error("REFRESH_ERROR: No data received from refresh endpoint.");
3472
- }
3473
- const accessTokenAfterRefresh = safeGet(data, accessTokenPathArray);
3474
- const refreshTokenAfterRefresh = safeGet(data, refreshTokenPathArray);
3475
- if (!accessTokenAfterRefresh ||
3476
- typeof accessTokenAfterRefresh !== "string") {
3477
- throw new Error(`REFRESH_ERROR: Access token not found or invalid at path '${accessTokenPath}' in refresh response.`);
3478
- }
3479
- if (!refreshTokenAfterRefresh ||
3480
- typeof refreshTokenAfterRefresh !== "string") {
3481
- throw new Error(`REFRESH_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in refresh response.`);
3482
- }
3483
- await storeAuthToken(accessTokenAfterRefresh, secretKey, await getSessionPersistence());
3484
- await storeAuthRefreshToken(refreshTokenAfterRefresh, secretKey, await getSessionPersistence());
3485
- return data;
3486
- }
3487
- catch (error) {
3488
- handleError(error, false);
3489
- await logout();
3490
- throw error;
3491
- }
3492
- };
3493
- return {
3494
- logout,
3495
- login,
3496
- refresh,
3497
- };
3498
- }
3499
-
3500
3489
  /**
3501
3490
  * The Vue plugin for @arex95/vue-core.
3502
3491
  * Configures the core functionalities for authentication and API communication.