@arex95/vue-core 1.1.36 → 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
@@ -709,117 +709,40 @@ function inferErrorType(error) {
709
709
  return 'error';
710
710
  }
711
711
 
712
- /**
713
- * Clears all stored authentication data (access and refresh tokens)
714
- * from either sessionStorage, localStorage, or both based on the provided location preference.
715
- *
716
- * @param {LocationPreference} location - The storage preference ('local' for localStorage, 'session' for sessionStorage, 'any' for both).
717
- * @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
718
- */
719
- const cleanCredentials = async (location) => {
720
- const tokensConfig = getTokenConfig();
721
- Object.keys(tokensConfig).forEach((key) => {
722
- const itemKey = tokensConfig[key];
723
- if (location === "local" || location === "any") {
724
- localStorage.removeItem(itemKey);
725
- }
726
- if (location === "session" || location === "any") {
727
- sessionStorage.removeItem(itemKey);
728
- }
729
- });
730
- };
731
- /**
732
- * Retrieves the authentication token (access token) from storage, decrypting it
733
- * using the provided secret key and based on the specified session preference.
734
- *
735
- * @param {string} secretKey - The secret key used for decryption.
736
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
737
- * @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
738
- */
739
- const getAuthToken = async (secretKey, location) => {
740
- const tokensConfig = getTokenConfig();
741
- return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
742
- };
743
- /**
744
- * Retrieves the authentication refresh token from storage, decrypting it
745
- * using the provided secret key and based on the specified session preference.
746
- *
747
- * @param {string} secretKey - The secret key used for decryption.
748
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
749
- * @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
750
- */
751
- const getAuthRefreshToken = async (secretKey, location) => {
752
- const tokensConfig = getTokenConfig();
753
- return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
712
+ let endpointsConfig = {
713
+ LOGIN: "/login",
714
+ REFRESH: "/refresh",
715
+ LOGOUT: "/logout",
754
716
  };
755
717
  /**
756
- * Stores the authentication token (access token) in storage after encrypting it,
757
- * based on the specified session preference.
718
+ * Configures authentication endpoint URLs globally.
719
+ * This function freezes the object to prevent further modifications.
758
720
  *
759
- * @param {string} token - The access token to store.
760
- * @param {string} secretKey - The secret key used for encryption.
761
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
762
- * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
763
- */
764
- const storeAuthToken = async (token, secretKey, location) => {
765
- const tokensConfig = getTokenConfig();
766
- await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
767
- };
768
- /**
769
- * Stores the authentication refresh token in storage after encrypting it,
770
- * based on the specified session preference.
721
+ * @param {EndpointConfig} config - An object containing the authentication endpoint URLs.
722
+ * @param {string} config.loginEndpoint - URL of the login endpoint.
723
+ * @param {string} config.refreshEndpoint - URL of the refresh token endpoint.
724
+ * @param {string} config.logoutEndpoint - URL of the logout endpoint.
771
725
  *
772
- * @param {string} token - The refresh token to store.
773
- * @param {string} secretKey - The secret key used for encryption.
774
- * @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
775
- * @returns {Promise<void>} A promise that resolves when the token is successfully stored.
726
+ * @returns {void} Does not return anything but freezes the endpoint configuration object.
776
727
  */
777
- const storeAuthRefreshToken = async (token, secretKey, location) => {
778
- const tokensConfig = getTokenConfig();
779
- await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
780
- };
728
+ function configEndpoints(config) {
729
+ endpointsConfig = Object.freeze({
730
+ LOGIN: config.loginEndpoint,
731
+ REFRESH: config.refreshEndpoint,
732
+ LOGOUT: config.logoutEndpoint,
733
+ });
734
+ }
781
735
  /**
782
- * Verifies the validity and expiration of the current authentication token.
783
- * If the token is missing, invalid, or expired, appropriate errors are thrown and credentials are cleaned.
736
+ * Retrieves the configured authentication endpoint URLs.
784
737
  *
785
- * @returns {Promise<boolean>} True if the token is valid and unexpired.
786
- * @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
787
- * "TOKEN_INVALID" if the token format is invalid.
738
+ * @returns {EndpointsConfig} An object containing the configured authentication endpoints.
739
+ * @property {string} LOGIN - URL of the login endpoint.
740
+ * @property {string} REFRESH - URL of the refresh token endpoint.
741
+ * @property {string} LOGOUT - URL of the logout endpoint.
788
742
  */
789
- const verifyAuth = async () => {
790
- const sessionPersistence = 'any';
791
- const handleAuthError = async (message, shouldClean = true) => {
792
- handleError(message, false);
793
- if (shouldClean) {
794
- await cleanCredentials(sessionPersistence);
795
- }
796
- return false;
797
- };
798
- try {
799
- const token = await getAuthToken(getAppKey(), sessionPersistence);
800
- if (!token) {
801
- return await handleAuthError("TOKEN_MISSING: No valid token found");
802
- }
803
- let decoded;
804
- try {
805
- decoded = jwtDecode(token);
806
- }
807
- catch (decodeError) {
808
- return await handleAuthError("TOKEN_INVALID: Invalid token format");
809
- }
810
- const currentTime = Date.now() / 1000;
811
- if (typeof decoded.exp !== "number") {
812
- return await handleAuthError("TOKEN_INVALID: Invalid expiration format");
813
- }
814
- if (decoded.exp <= currentTime) {
815
- return await handleAuthError("TOKEN_EXPIRED: Token is expired");
816
- }
817
- return true;
818
- }
819
- catch (error) {
820
- return await handleAuthError("AUTH_ERROR: An unexpected error occurred", true);
821
- }
822
- };
743
+ function getEndpointsConfig() {
744
+ return endpointsConfig;
745
+ }
823
746
 
824
747
  let appKey = null;
825
748
  /**
@@ -958,193 +881,677 @@ async function getSessionConfig() {
958
881
  return _sessionConfig;
959
882
  }
960
883
 
961
- let endpointsConfig = {
962
- LOGIN: "/login",
963
- REFRESH: "/refresh",
964
- LOGOUT: "/logout",
884
+ /**
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.
890
+ */
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
+ });
965
902
  };
966
903
  /**
967
- * Configures authentication endpoint URLs globally.
968
- * This function freezes the object to prevent further modifications.
904
+ * Retrieves the authentication token (access token) from storage, decrypting it
905
+ * using the provided secret key and based on the specified session preference.
969
906
  *
970
- * @param {EndpointConfig} config - An object containing the authentication endpoint URLs.
971
- * @param {string} config.loginEndpoint - URL of the login endpoint.
972
- * @param {string} config.refreshEndpoint - URL of the refresh token endpoint.
973
- * @param {string} config.logoutEndpoint - URL of the logout endpoint.
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.
910
+ */
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.
974
918
  *
975
- * @returns {void} Does not return anything but freezes the endpoint configuration object.
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.
976
922
  */
977
- function configEndpoints(config) {
978
- endpointsConfig = Object.freeze({
979
- LOGIN: config.loginEndpoint,
980
- REFRESH: config.refreshEndpoint,
981
- LOGOUT: config.logoutEndpoint,
982
- });
983
- }
923
+ const getAuthRefreshToken = async (secretKey, location) => {
924
+ const tokensConfig = getTokenConfig();
925
+ return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
926
+ };
984
927
  /**
985
- * Retrieves the configured authentication endpoint URLs.
928
+ * Stores the authentication token (access token) in storage after encrypting it,
929
+ * based on the specified session preference.
986
930
  *
987
- * @returns {EndpointsConfig} An object containing the configured authentication endpoints.
988
- * @property {string} LOGIN - URL of the login endpoint.
989
- * @property {string} REFRESH - URL of the refresh token endpoint.
990
- * @property {string} LOGOUT - URL of the logout endpoint.
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.
991
935
  */
992
- function getEndpointsConfig() {
993
- return endpointsConfig;
994
- }
995
-
936
+ const storeAuthToken = async (token, secretKey, location) => {
937
+ const tokensConfig = getTokenConfig();
938
+ await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
939
+ };
996
940
  /**
997
- * AxiosService class encapsulates the Axios configuration and logic.
998
- * It manages request and response interceptors and provides methods for making HTTP requests.
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.
999
948
  */
1000
- class AxiosService {
1001
- instance;
1002
- cancelTokenSource;
1003
- activeRequests = 0;
1004
- refreshTokenInProgress = false;
1005
- refreshTokenUrl;
1006
- /**
1007
- * Initializes the AxiosService instance by creating an Axios instance with default configuration
1008
- * and setting up request and response interceptors.
1009
- */
1010
- constructor(url, headers = {}) {
1011
- this.cancelTokenSource = axios.CancelToken.source();
1012
- this.instance = axios.create({
1013
- baseURL: url ?? '',
1014
- timeout: 300000,
1015
- headers: {
1016
- 'Accept': 'application/json',
1017
- ...headers,
1018
- },
1019
- withCredentials: false,
1020
- });
1021
- const endpointsConfig = getEndpointsConfig();
1022
- this.refreshTokenUrl = endpointsConfig.REFRESH;
1023
- this.initializeInterceptors();
1024
- }
1025
- /**
1026
- * Initializes request and response interceptors for the Axios instance.
1027
- */
1028
- initializeInterceptors() {
1029
- this.instance.interceptors.request.use(async (config) => {
1030
- const token = await getAuthToken(getAppKey(), 'any');
1031
- if (token && config.headers) {
1032
- config.headers.Authorization = `Bearer ${token}`;
1033
- }
1034
- config.cancelToken = this.cancelTokenSource.token;
1035
- this.activeRequests++;
1036
- return config;
1037
- }, (error) => {
1038
- handleError(error, false);
1039
- this.activeRequests++;
1040
- return Promise.reject(error);
1041
- });
1042
- this.instance.interceptors.response.use((response) => {
1043
- this.activeRequests--;
1044
- return response;
1045
- }, async (error) => {
1046
- this.activeRequests--;
1047
- if (axios.isAxiosError(error) && error.response?.status === 401 && !this.refreshTokenInProgress) {
1048
- const refreshToken = await getAuthRefreshToken(getAppKey(), "any");
1049
- if (refreshToken) {
1050
- this.refreshTokenInProgress = true;
1051
- try {
1052
- const refreshResponse = await axios.post(this.refreshTokenUrl, { refreshToken });
1053
- const { token: newAccessToken } = refreshResponse.data;
1054
- if (newAccessToken) {
1055
- storeAuthToken(newAccessToken, getAppKey(), await getSessionPersistence());
1056
- if (error.config && error.config.headers) {
1057
- error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
1058
- }
1059
- this.refreshTokenInProgress = false;
1060
- if (error.config) {
1061
- return this.instance(error.config);
1062
- }
1063
- }
1064
- }
1065
- catch (refreshError) {
1066
- handleError(refreshError, false);
1067
- this.refreshTokenInProgress = false;
1068
- cleanCredentials('any');
1069
- }
1070
- }
1071
- }
1072
- handleError(error, false);
1073
- return Promise.reject(error);
1074
- });
1075
- }
1076
- /**
1077
- * Returns the number of active requests.
1078
- */
1079
- getActiveRequests() {
1080
- return this.activeRequests;
1081
- }
1082
- /**
1083
- * Returns the Axios instance with the configured settings and interceptors.
1084
- * @returns {AxiosInstance} The configured Axios instance.
1085
- */
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
+ }
1086
1107
  getAxiosInstance() {
1087
1108
  return this.instance;
1088
1109
  }
1089
- /**
1090
- * Cancels all ongoing requests.
1091
- */
1092
1110
  cancelAllRequests() {
1093
- this.cancelTokenSource.cancel('Operation canceled by the user.');
1111
+ this.cancelTokenSource.cancel("Operation canceled by the user.");
1094
1112
  this.cancelTokenSource = axios.CancelToken.source();
1095
1113
  }
1096
- /**
1097
- * Sets a new header for the Axios instance.
1098
- * @param {string} key - The header key.
1099
- * @param {string} value - The header value.
1100
- */
1101
1114
  setHeader(key, value) {
1102
1115
  this.instance.defaults.headers.common[key] = value;
1103
1116
  }
1104
- /**
1105
- * Removes a header from the Axios instance.
1106
- * @param {string} key - The header key to remove.
1107
- */
1108
1117
  removeHeader(key) {
1109
1118
  delete this.instance.defaults.headers.common[key];
1110
1119
  }
1111
1120
  }
1112
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
+
1113
1534
  let axiosInstance;
1114
- /**
1115
- * Configures the global Axios instance with a base URL.
1116
- *
1117
- * @param {AxiosConfig} config - An object containing the base URL for the Axios instance.
1118
- * @param {string} config.baseURL - The base URL for the Axios instance.
1119
- *
1120
- * @returns {void}
1121
- */
1535
+ const auth = useAuth();
1536
+ const refreshAndForget = async () => {
1537
+ await auth.refresh();
1538
+ };
1122
1539
  const configAxios = (config) => {
1123
- axiosInstance = new AxiosService(config.baseURL);
1540
+ axiosInstance = new AxiosService({
1541
+ baseURL: config.baseURL,
1542
+ }, refreshAndForget);
1124
1543
  };
1125
- /**
1126
- * Retrieves the configured Axios instance.
1127
- *
1128
- * @returns {AxiosService} The configured Axios instance.
1129
- * @throws Will throw an error if the Axios instance is not configured.
1130
- */
1131
1544
  const getAxiosInstance = () => {
1132
1545
  if (!axiosInstance) {
1133
1546
  throw new Error("Axios instance not configured. Call configAxios first.");
1134
1547
  }
1135
1548
  return axiosInstance.getAxiosInstance();
1136
1549
  };
1137
- /**
1138
- * Creates a new AxiosService instance with custom headers.
1139
- *
1140
- * @param {CustomAxiosConfig} config - An object containing the base URL and optional custom headers.
1141
- * @param {string} config.baseURL - The base URL for the Axios instance.
1142
- * @param {Record<string, string>} [config.headers] - Custom headers to set (optional).
1143
- *
1144
- * @returns {AxiosService} The new AxiosService instance.
1145
- */
1146
1550
  const createCustomAxiosInstance = (config) => {
1147
- return new AxiosService(config.baseURL, config.headers).getAxiosInstance();
1551
+ return new AxiosService({
1552
+ baseURL: config.baseURL,
1553
+ headers: config.headers,
1554
+ }, refreshAndForget).getAxiosInstance();
1148
1555
  };
1149
1556
 
1150
1557
  /**
@@ -2060,342 +2467,61 @@ function readFileAsText(file) {
2060
2467
  * @param {File} file The file to read.
2061
2468
  * @returns {Promise<string>} A promise that resolves with the Data URL.
2062
2469
  */
2063
- function readFileAsDataURL(file) {
2064
- return new Promise((resolve, reject) => {
2065
- const reader = new FileReader();
2066
- reader.onload = () => resolve(reader.result);
2067
- reader.onerror = reject;
2068
- reader.readAsDataURL(file);
2069
- });
2070
- }
2071
- /**
2072
- * Creates a Blob from a string.
2073
- * @param {string} content The string content for the Blob.
2074
- * @param {string} [type='text/plain'] The MIME type of the Blob.
2075
- * @returns {Blob} The Blob object.
2076
- */
2077
- function stringToBlob(content, type = 'text/plain') {
2078
- return new Blob([content], { type });
2079
- }
2080
- /**
2081
- * Creates a Blob from an ArrayBuffer.
2082
- * @param {ArrayBuffer} buffer The ArrayBuffer to convert.
2083
- * @param {string} [type='application/octet-stream'] The MIME type of the Blob.
2084
- * @returns {Blob} The Blob object.
2085
- */
2086
- function bufferToBlob(buffer, type = 'application/octet-stream') {
2087
- return new Blob([buffer], { type });
2088
- }
2089
- /**
2090
- * Creates and downloads a file from Blob data.
2091
- * @param {Blob} blob The Blob containing the file data.
2092
- * @param {string} fileName The name of the file to create.
2093
- */
2094
- function downloadBlob(blob, fileName) {
2095
- const link = document.createElement('a');
2096
- const url = URL.createObjectURL(blob);
2097
- link.setAttribute('href', url);
2098
- link.setAttribute('download', fileName);
2099
- // Append link to the body and trigger a click
2100
- document.body.appendChild(link);
2101
- link.click();
2102
- // Clean up
2103
- document.body.removeChild(link);
2104
- URL.revokeObjectURL(url);
2105
- }
2106
- /**
2107
- * Creates a FormData object containing a Blob.
2108
- * @param {Blob} blob The Blob to include in the FormData.
2109
- * @param {string} name The name of the form field.
2110
- * @param {string} [fileName='file'] The file name for the Blob.
2111
- * @returns {FormData} The FormData object.
2112
- */
2113
- function blobToFormData(blob, name, fileName = 'file') {
2114
- const formData = new FormData();
2115
- formData.append(name, blob, fileName);
2116
- return formData;
2117
- }
2118
-
2119
- /**
2120
- * Converts a Proxy object to a plain object.
2121
- * @param {ProxyConstructor} proxy The Proxy object to convert.
2122
- * @returns {Object} The plain object.
2123
- */
2124
- function proxyToPlainObject(proxy) {
2125
- if (!proxy)
2126
- return {};
2127
- const plainObject = {};
2128
- for (const property of Object.keys(proxy)) {
2129
- plainObject[property] = proxy[property];
2130
- }
2131
- return plainObject;
2132
- }
2133
- /**
2134
- * Compares two objects to check if they have the same keys.
2135
- * @param {Object} object1 The first object to compare.
2136
- * @param {Object} object2 The second object to compare.
2137
- * @returns {boolean} True if the objects have the same keys, otherwise false.
2138
- */
2139
- function compareObject(object1, object2) {
2140
- return Object.keys(object1).every(function (element) {
2141
- return Object.keys(object2).includes(element);
2142
- });
2143
- }
2144
- /**
2145
- * Deeply compares two objects to check if they are equal.
2146
- * @param {Object} object1 The first object to compare.
2147
- * @param {Object} object2 The second object to compare.
2148
- * @returns {boolean} True if the objects are deeply equal, otherwise false.
2149
- */
2150
- function deepEqual(object1, object2) {
2151
- if (object1 === object2)
2152
- return true;
2153
- if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
2154
- return false;
2155
- }
2156
- const keys1 = Object.keys(object1);
2157
- const keys2 = Object.keys(object2);
2158
- if (keys1.length !== keys2.length) {
2159
- return false;
2160
- }
2161
- for (const key of keys1) {
2162
- if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
2163
- return false;
2164
- }
2165
- }
2166
- return true;
2167
- }
2168
- /**
2169
- * Deeply clones an object.
2170
- * @param {Object} obj The object to clone.
2171
- * @returns {Object} The cloned object.
2172
- */
2173
- function deepClone(obj) {
2174
- if (obj === null || typeof obj !== 'object') {
2175
- return obj;
2176
- }
2177
- if (obj instanceof Date) {
2178
- return new Date(obj.getTime());
2179
- }
2180
- if (obj instanceof Array) {
2181
- return obj.map(item => deepClone(item));
2182
- }
2183
- if (obj instanceof Object) {
2184
- const copy = {};
2185
- for (const key in obj) {
2186
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
2187
- copy[key] = deepClone(obj[key]);
2188
- }
2189
- }
2190
- return copy;
2191
- }
2192
- throw new Error('Unable to clone object! Its type is not supported.');
2193
- }
2194
- /**
2195
- * Converts an object to a query string.
2196
- * @param {Object} obj The object to convert.
2197
- * @returns {string} The query string.
2198
- */
2199
- function objectToQueryString(obj) {
2200
- return Object.keys(obj)
2201
- .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
2202
- .join('&');
2203
- }
2204
- // Example usage:
2205
- // objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
2206
- /**
2207
- * Gets the differences between two objects.
2208
- * @param {Object} object1 The first object.
2209
- * @param {Object} object2 The second object.
2210
- * @returns {Object} An object containing the differences.
2211
- */
2212
- function getObjectDifferences(object1, object2) {
2213
- const differences = {};
2214
- const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
2215
- for (const key of keys) {
2216
- if (object1[key] !== object2[key]) {
2217
- differences[key] = { object1: object1[key], object2: object2[key] };
2218
- }
2219
- }
2220
- return differences;
2221
- }
2222
- /**
2223
- * Filters an object by a list of keys.
2224
- * @param {Object} obj The object to filter.
2225
- * @param {Array<string>} keys The keys to keep.
2226
- * @returns {Object} The filtered object.
2227
- */
2228
- function filterObjectByKeys(obj, keys) {
2229
- const filteredObject = {};
2230
- for (const key of keys) {
2231
- if (key in obj) {
2232
- filteredObject[key] = obj[key];
2233
- }
2234
- }
2235
- return filteredObject;
2236
- }
2237
- // Example usage:
2238
- // filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
2239
- /**
2240
- * Deeply merges two objects.
2241
- * @param {Object} target The target object to merge into.
2242
- * @param {Object} source The source object to merge from.
2243
- * @returns {Object} The merged object.
2244
- */
2245
- function deepMerge(target, source) {
2246
- if (target === null || typeof target !== 'object' || typeof source !== 'object') {
2247
- return target;
2248
- }
2249
- for (const key in source) {
2250
- if (Object.prototype.hasOwnProperty.call(source, key)) {
2251
- if (source[key] && typeof source[key] === 'object') {
2252
- if (target && !target[key]) {
2253
- Object.assign(target, { [key]: {} });
2254
- }
2255
- deepMerge(target[key], source[key]);
2256
- }
2257
- else {
2258
- Object.assign(target, { [key]: source[key] });
2259
- }
2260
- }
2261
- }
2262
- return target;
2263
- }
2264
- /**
2265
- * Checks if an object is empty.
2266
- * @param {Object} obj The object to check.
2267
- * @returns {boolean} True if the object is empty, otherwise false.
2268
- */
2269
- function isEmptyObject(obj) {
2270
- return Object.keys(obj).length === 0;
2271
- }
2272
- /**
2273
- * Safely accesses nested properties in an object.
2274
- * @param {Object} obj The object to access.
2275
- * @param {Array<string>} keys The array of keys representing the path.
2276
- * @returns {any} The value at the nested path, or undefined if not found.
2277
- */
2278
- function safeGet(obj, keys) {
2279
- return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
2280
- }
2281
- // Example usage:
2282
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
2283
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
2284
- /**
2285
- * Removes empty properties (null, undefined, or empty string) from an object.
2286
- * @param {Object} obj The object to clean.
2287
- * @returns {Object} A new object without empty properties.
2288
- */
2289
- function removeEmptyProperties(obj) {
2290
- return Object.keys(obj)
2291
- .filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
2292
- .reduce((acc, key) => {
2293
- acc[key] = obj[key];
2294
- return acc;
2295
- }, {});
2296
- }
2297
- // Example usage:
2298
- // removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
2299
- /**
2300
- * Retrieves all keys of an object as an array.
2301
- * @param {Object} obj The object to retrieve keys from.
2302
- * @returns {Array<string>} The array of keys.
2303
- */
2304
- function getObjectKeys(obj) {
2305
- 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
+ });
2306
2477
  }
2307
- // Example usage:
2308
- // getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
2309
2478
  /**
2310
- * Checks if an object has nested properties.
2311
- * @param {Object} obj The object to check.
2312
- * @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.
2313
2483
  */
2314
- function hasNestedProperties(obj) {
2315
- return Object.values(obj).some(value => typeof value === 'object' && value !== null);
2484
+ function stringToBlob(content, type = 'text/plain') {
2485
+ return new Blob([content], { type });
2316
2486
  }
2317
- // Example usage:
2318
- // hasNestedProperties({ a: 1, b: { c: 2 } }); // true
2319
- // hasNestedProperties({ a: 1, b: 2 }); // false
2320
2487
  /**
2321
- * Converts an object to FormData, handling nested objects.
2322
- * @param {Object} obj The object to convert.
2323
- * @param {FormData} [formData] The FormData object to append to.
2324
- * @param {string} [parentKey] The parent key for nested objects.
2325
- * @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.
2326
2492
  */
2327
- function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
2328
- Object.entries(obj).forEach(([key, value]) => {
2329
- const finalKey = parentKey ? `${parentKey}[${key}]` : key;
2330
- if (value && typeof value === 'object' && !(value instanceof File)) {
2331
- objectToFormDataEnhanced(value, formData, finalKey);
2332
- }
2333
- else {
2334
- formData.append(finalKey, value);
2335
- }
2336
- });
2337
- return formData;
2493
+ function bufferToBlob(buffer, type = 'application/octet-stream') {
2494
+ return new Blob([buffer], { type });
2338
2495
  }
2339
- // Example usage:
2340
- // objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
2341
2496
  /**
2342
- * Converts a JavaScript object into FormData.
2343
- *
2344
- * @param obj - The object to be converted.
2345
- * @param form - An optional FormData instance to use.
2346
- * @param namespace - An optional namespace to use for nested objects.
2347
- * @returns The FormData instance with the object's key-value pairs.
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.
2348
2500
  */
2349
- const objectToFormData = function (obj, form, namespace) {
2350
- const fd = form || new FormData();
2351
- let formKey;
2352
- for (const property in obj) {
2353
- if (obj[property] === undefined) {
2354
- continue;
2355
- }
2356
- if (Object.prototype.hasOwnProperty.call(obj, property)) {
2357
- if (namespace) {
2358
- formKey = `${namespace}[${property}]`;
2359
- }
2360
- else {
2361
- formKey = property;
2362
- }
2363
- if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
2364
- // Recursively handle nested objects
2365
- objectToFormData(obj[property], fd, formKey);
2366
- }
2367
- else {
2368
- // Convert boolean values to 1/0
2369
- const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
2370
- fd.append(formKey, value);
2371
- }
2372
- }
2373
- }
2374
- return fd;
2375
- };
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
+ }
2376
2513
  /**
2377
- * Flattens a nested object, bringing all properties to the top level.
2378
- * @param {Object} obj The object to flatten.
2379
- * @param {string} [parentKey] The parent key for nested properties.
2380
- * @param {Object} [result] The resulting flattened object.
2381
- * @returns {Object} The flattened object.
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.
2382
2519
  */
2383
- function flattenObject(obj, parentKey = '', result = {}) {
2384
- for (const key in obj) {
2385
- if (obj.hasOwnProperty(key)) {
2386
- const propName = parentKey ? `${parentKey}.${key}` : key;
2387
- if (typeof obj[key] === 'object' && obj[key] !== null) {
2388
- flattenObject(obj[key], propName, result);
2389
- }
2390
- else {
2391
- result[propName] = obj[key];
2392
- }
2393
- }
2394
- }
2395
- return result;
2520
+ function blobToFormData(blob, name, fileName = 'file') {
2521
+ const formData = new FormData();
2522
+ formData.append(name, blob, fileName);
2523
+ return formData;
2396
2524
  }
2397
- // Example usage:
2398
- // flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
2399
2525
 
2400
2526
  /**
2401
2527
  * Capitalizes the first character of a string.
@@ -3360,137 +3486,6 @@ function useSorter(items, criteriaList, selectedCriteria) {
3360
3486
  }).value;
3361
3487
  }
3362
3488
 
3363
- /**
3364
- * @typedef {object} AuthHook
3365
- * @property {function(): Promise<number | null>} getTokenExpiry - Retrieves the expiration time of the current token in milliseconds.
3366
- * @property {function(): Promise<void>} cleanCredentials - Clears authentication credentials from storage.
3367
- * @property {(params?: AuthParams) => Promise<void>} logout - Logs out the user, clears credentials, and reloads the page.
3368
- * @property {(params: AuthParams, persistence: SessionPreference, tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} login - Logs in the user and stores tokens.
3369
- * @property {(tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>} refresh - Refreshes authentication tokens.
3370
- * @property {function(): Promise<boolean>} verifyAuth - Verifies the validity and expiration of the current authentication token.
3371
- * @property {(preference: SessionPreference) => void} setSessionPersistencePreference - Sets the user's preferred storage for authentication data.
3372
- * @property {function(): SessionPreference} getSessionPersistence - Retrieves the user's current preferred storage for authentication data.
3373
- */
3374
- /**
3375
- * Custom hook for authentication logic, including login, logout, token management, and session preference.
3376
- *
3377
- * @param {string} secretKey - The secret key used for token encryption/decryption.
3378
- * @returns {AuthHook} An object containing authentication functions.
3379
- */
3380
- function useAuth(secretKey = getAppKey()) {
3381
- const axiosInstance = getAxiosInstance();
3382
- const endpoints = getEndpointsConfig();
3383
- /**
3384
- * Logs out the user by making a POST request to the logout endpoint,
3385
- * cleaning all stored credentials, and reloading the page.
3386
- * The session persistence preference is NOT reset here; it persists across logouts.
3387
- *
3388
- * @param {AuthParams} [params={}] - Optional parameters to send with the logout request.
3389
- * @returns {Promise<void>}
3390
- */
3391
- const logout = async (params = {}) => {
3392
- try {
3393
- await axiosInstance.post(endpoints.LOGOUT, params);
3394
- }
3395
- catch (error) {
3396
- handleError(error, false);
3397
- }
3398
- finally {
3399
- await cleanCredentials(await getSessionPersistence());
3400
- window.location.reload();
3401
- }
3402
- };
3403
- /**
3404
- * Authenticates the user by making a POST request to the login endpoint,
3405
- * stores the received access and refresh tokens, and sets the session persistence preference.
3406
- *
3407
- * @param {AuthParams} params - The authentication parameters (e.g., username, password).
3408
- * @param {SessionPreference} persistence - The storage preference: 'local' for localStorage, 'session' for sessionStorage.
3409
- * @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.
3410
- * @returns {Promise<AuthResponse>} La respuesta de autenticación que contiene los tokens e información del usuario.
3411
- * @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.
3412
- */
3413
- const login = async (params, persistence, tokenPaths) => {
3414
- try {
3415
- const { data } = await axiosInstance.post(endpoints.LOGIN, params);
3416
- const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
3417
- const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
3418
- const accessTokenPathArray = accessTokenPath.split(".");
3419
- const refreshTokenPathArray = refreshTokenPath.split(".");
3420
- if (!data) {
3421
- throw new Error("LOGIN_ERROR: No data received from login endpoint.");
3422
- }
3423
- const accessToken = safeGet(data, accessTokenPathArray);
3424
- const refreshToken = safeGet(data, refreshTokenPathArray);
3425
- if (!accessToken || typeof accessToken !== "string") {
3426
- throw new Error(`LOGIN_ERROR: Access token not found or invalid at path '${accessTokenPath}' in response.`);
3427
- }
3428
- if (!refreshToken || typeof refreshToken !== "string") {
3429
- throw new Error(`LOGIN_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in response.`);
3430
- }
3431
- configSession({
3432
- persistencePreference: persistence,
3433
- });
3434
- await storeAuthToken(accessToken, secretKey, persistence);
3435
- await storeAuthRefreshToken(refreshToken, secretKey, persistence);
3436
- return data;
3437
- }
3438
- catch (error) {
3439
- handleError(error, false);
3440
- throw error;
3441
- }
3442
- };
3443
- /**
3444
- * Refreshes the authentication tokens using the stored refresh token.
3445
- * This function can also accept optional token paths if the refresh endpoint
3446
- * returns tokens with a different structure than the default login.
3447
- * If no refresh token is found, it throws an error and initiates a logout.
3448
- *
3449
- * @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.
3450
- * @returns {Promise<AuthResponse>} The new authentication response with refreshed tokens.
3451
- * @throws {Error} If the refresh token is missing or the refresh request fails.
3452
- */
3453
- const refresh = async (tokenPaths) => {
3454
- try {
3455
- const refreshTokenFromStorage = await getAuthRefreshToken(secretKey, await getSessionPersistence());
3456
- if (!refreshTokenFromStorage) {
3457
- throw new Error("TOKEN_MISSING: No refresh token found in storage.");
3458
- }
3459
- const { data } = await axiosInstance.post(endpoints.REFRESH, { refresh_token: refreshTokenFromStorage });
3460
- const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
3461
- const refreshTokenPath = tokenPaths?.refreshTokenPath || "refresh_token";
3462
- const accessTokenPathArray = accessTokenPath.split(".");
3463
- const refreshTokenPathArray = refreshTokenPath.split(".");
3464
- if (!data) {
3465
- throw new Error("REFRESH_ERROR: No data received from refresh endpoint.");
3466
- }
3467
- const accessTokenAfterRefresh = safeGet(data, accessTokenPathArray);
3468
- const refreshTokenAfterRefresh = safeGet(data, refreshTokenPathArray);
3469
- if (!accessTokenAfterRefresh ||
3470
- typeof accessTokenAfterRefresh !== "string") {
3471
- throw new Error(`REFRESH_ERROR: Access token not found or invalid at path '${accessTokenPath}' in refresh response.`);
3472
- }
3473
- if (!refreshTokenAfterRefresh ||
3474
- typeof refreshTokenAfterRefresh !== "string") {
3475
- throw new Error(`REFRESH_ERROR: Refresh token not found or invalid at path '${refreshTokenPath}' in refresh response.`);
3476
- }
3477
- await storeAuthToken(accessTokenAfterRefresh, secretKey, await getSessionPersistence());
3478
- await storeAuthRefreshToken(refreshTokenAfterRefresh, secretKey, await getSessionPersistence());
3479
- return data;
3480
- }
3481
- catch (error) {
3482
- handleError(error, false);
3483
- await logout();
3484
- throw error;
3485
- }
3486
- };
3487
- return {
3488
- logout,
3489
- login,
3490
- refresh,
3491
- };
3492
- }
3493
-
3494
3489
  /**
3495
3490
  * The Vue plugin for @arex95/vue-core.
3496
3491
  * Configures the core functionalities for authentication and API communication.