@arex95/vue-core 1.1.43 → 3.0.1
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/README.md +90 -57
- package/dist/composables/auth/useAuth.d.ts +20 -6
- package/dist/composables/axios/axiosFetch.d.ts +7 -5
- package/dist/composables/axios/index.d.ts +0 -1
- package/dist/composables/axios/useFetch.d.ts +14 -5
- package/dist/composables/breakpoints/useBreakpoint.d.ts +11 -2
- package/dist/composables/filters/useFilter.d.ts +13 -8
- package/dist/composables/monitoring/useApiActivity.d.ts +17 -6
- package/dist/composables/monitoring/useUserActivity.d.ts +20 -5
- package/dist/composables/paginators/usePaginator.d.ts +13 -5
- package/dist/composables/sorters/useSorter.d.ts +13 -7
- package/dist/config/auth/authFetcher.d.ts +30 -0
- package/dist/config/auth/index.d.ts +1 -0
- package/dist/config/axios/axiosConfig.d.ts +30 -0
- package/dist/config/axios/axiosInstance.d.ts +15 -1
- package/dist/config/global/endpointsConfig.d.ts +9 -13
- package/dist/config/global/keyConfig.d.ts +7 -10
- package/dist/config/global/sessionConfig.d.ts +15 -17
- package/dist/config/global/tokenPathsConfig.d.ts +17 -18
- package/dist/config/global/tokensConfig.d.ts +9 -9
- package/dist/config/index.d.ts +1 -0
- package/dist/enums/breakpointsEnums.d.ts +7 -4
- package/dist/enums/errorsEnums.d.ts +26 -19
- package/dist/enums/fileTypesEnums.d.ts +33 -1
- package/dist/enums/httpExceptionsEnums.d.ts +3 -1
- package/dist/enums/keyCodesEnums.d.ts +2 -4
- package/dist/enums/storageEnums.d.ts +4 -4
- package/dist/errors/AuthError.d.ts +10 -0
- package/dist/errors/BaseError.d.ts +16 -0
- package/dist/errors/NetworkError.d.ts +9 -0
- package/dist/errors/ServerError.d.ts +10 -0
- package/dist/errors/ValidationError.d.ts +14 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/fetchers/axios.d.ts +22 -0
- package/dist/fetchers/index.d.ts +2 -0
- package/dist/fetchers/ofetch.d.ts +33 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.mjs +1631 -875
- package/dist/rest/RestStd.d.ts +146 -102
- package/dist/services/credentials.d.ts +24 -29
- package/dist/services/extractTokens.d.ts +7 -6
- package/dist/services/refreshTokens.d.ts +11 -12
- package/dist/services/storeTokens.d.ts +8 -6
- package/dist/types/AppKeyConfig.d.ts +7 -0
- package/dist/types/ArexVueCoreOptions.d.ts +20 -0
- package/dist/types/Auth.d.ts +15 -0
- package/dist/types/AxiosOptionsParameter.d.ts +14 -7
- package/dist/types/AxiosServiceOptions.d.ts +9 -0
- package/dist/types/DecodedJwtPayload.d.ts +12 -0
- package/dist/types/EndpointsConfig.d.ts +7 -0
- package/dist/types/ErrorType.d.ts +4 -2
- package/dist/types/ExtendedQueryOptions.d.ts +10 -0
- package/dist/types/Fetcher.d.ts +24 -0
- package/dist/types/RestStdOptions.d.ts +62 -0
- package/dist/types/SessionConfig.d.ts +25 -1
- package/dist/types/TokenConfig.d.ts +7 -0
- package/dist/types/TokenValidationResult.d.ts +7 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/browser.d.ts +20 -14
- package/dist/utils/dates.d.ts +47 -34
- package/dist/utils/debounces.d.ts +54 -32
- package/dist/utils/encryption.d.ts +28 -24
- package/dist/utils/errors.d.ts +27 -8
- package/dist/utils/exports.d.ts +24 -19
- package/dist/utils/files.d.ts +33 -25
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/io.d.ts +70 -54
- package/dist/utils/objects.d.ts +78 -60
- package/dist/utils/retry.d.ts +8 -0
- package/dist/utils/ssr.d.ts +27 -0
- package/dist/utils/storage.d.ts +20 -14
- package/dist/utils/strings.d.ts +42 -31
- package/dist/utils/validations.d.ts +76 -57
- package/package.json +7 -16
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
2
2
|
import { jwtDecode } from 'jwt-decode';
|
|
3
|
-
import { useRouter } from 'vue-router';
|
|
4
3
|
import { v4 } from 'uuid';
|
|
5
4
|
import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { ref, watch, computed } from 'vue';
|
|
6
|
+
import { $fetch } from 'ofetch';
|
|
8
7
|
|
|
9
8
|
let tokensConfig = Object.freeze({
|
|
10
9
|
ACCESS_TOKEN: "access_token",
|
|
11
10
|
REFRESH_TOKEN: "refresh_token",
|
|
12
11
|
});
|
|
13
12
|
/**
|
|
14
|
-
* Configures the global keys for access and refresh tokens.
|
|
15
|
-
*
|
|
13
|
+
* Configures the global storage keys for the access and refresh tokens.
|
|
14
|
+
* This function should be called once at application startup to define the keys
|
|
15
|
+
* used for storing tokens in `localStorage` or `sessionStorage`. The configuration
|
|
16
|
+
* is frozen to prevent runtime changes.
|
|
16
17
|
*
|
|
17
|
-
* @param {TokenKeyConfig} config - An object containing the token keys.
|
|
18
|
-
* @param {string} config.accessTokenKey - The
|
|
19
|
-
* @param {string} config.refreshTokenKey - The
|
|
20
|
-
*
|
|
21
|
-
* @returns {void} Does not return anything, but freezes the token configuration object.
|
|
18
|
+
* @param {TokenKeyConfig} config - An object containing the token storage keys.
|
|
19
|
+
* @param {string} config.accessTokenKey - The key for the access token.
|
|
20
|
+
* @param {string} config.refreshTokenKey - The key for the refresh token.
|
|
22
21
|
*/
|
|
23
22
|
function configTokenKeys(config) {
|
|
24
23
|
tokensConfig = Object.freeze({
|
|
@@ -27,18 +26,19 @@ function configTokenKeys(config) {
|
|
|
27
26
|
});
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Retrieves the
|
|
29
|
+
* Retrieves the configured storage keys for the access and refresh tokens.
|
|
31
30
|
*
|
|
32
|
-
* @returns {TokensConfig}
|
|
31
|
+
* @returns {TokensConfig} A frozen object containing the `ACCESS_TOKEN` and `REFRESH_TOKEN` keys.
|
|
33
32
|
*/
|
|
34
33
|
function getTokenConfig() {
|
|
35
34
|
return tokensConfig;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
* Converts an ArrayBuffer or Uint8Array
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
38
|
+
* Converts an `ArrayBuffer` or `Uint8Array` into a hexadecimal string representation.
|
|
39
|
+
*
|
|
40
|
+
* @param {ArrayBuffer | Uint8Array} buffer - The buffer to convert.
|
|
41
|
+
* @returns {string} The resulting hexadecimal string.
|
|
42
42
|
*/
|
|
43
43
|
function ab2hex(buffer) {
|
|
44
44
|
return Array.from(new Uint8Array(buffer))
|
|
@@ -46,11 +46,12 @@ function ab2hex(buffer) {
|
|
|
46
46
|
.join("");
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Converts a hexadecimal string
|
|
50
|
-
*
|
|
51
|
-
* @
|
|
49
|
+
* Converts a hexadecimal string into a `Uint8Array`.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} hex - The hexadecimal string to convert.
|
|
52
|
+
* @returns {Uint8Array} The resulting `Uint8Array`.
|
|
52
53
|
* @throws {TypeError} If the input is not a string.
|
|
53
|
-
* @throws {Error} If the hexadecimal string
|
|
54
|
+
* @throws {Error} If the hexadecimal string has an invalid format or an odd length.
|
|
54
55
|
*/
|
|
55
56
|
function hex2ab(hex) {
|
|
56
57
|
if (typeof hex !== "string") {
|
|
@@ -69,10 +70,12 @@ function hex2ab(hex) {
|
|
|
69
70
|
return array;
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
|
-
* Derives
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
73
|
+
* Derives a `CryptoKey` for AES-CBC encryption from a plain-text secret key.
|
|
74
|
+
* It uses SHA-256 to hash the secret key, ensuring a fixed-length key suitable for the Web Crypto API.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} secretKey - The plain-text secret key.
|
|
77
|
+
* @returns {Promise<CryptoKey>} A promise that resolves with the derived `CryptoKey`.
|
|
78
|
+
* @throws {Error} If the `secretKey` is null or empty.
|
|
76
79
|
*/
|
|
77
80
|
async function importKey(secretKey) {
|
|
78
81
|
if (!secretKey) {
|
|
@@ -83,11 +86,13 @@ async function importKey(secretKey) {
|
|
|
83
86
|
return crypto.subtle.importKey("raw", digest, { name: "AES-CBC", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
|
-
* Encrypts a value with
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @
|
|
90
|
-
* @
|
|
89
|
+
* Encrypts a plain-text value using AES-CBC with a given secret key.
|
|
90
|
+
* A random 16-byte initialization vector (IV) is generated for each encryption.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} value - The plain-text string to encrypt.
|
|
93
|
+
* @param {string} secretKey - The secret key to use for encryption.
|
|
94
|
+
* @returns {Promise<string>} A promise that resolves with a concatenated hexadecimal string of the IV and the ciphertext.
|
|
95
|
+
* @throws {Error} If the `secretKey` is null or empty.
|
|
91
96
|
*/
|
|
92
97
|
async function encrypt(value, secretKey) {
|
|
93
98
|
const key = await importKey(secretKey);
|
|
@@ -97,14 +102,12 @@ async function encrypt(value, secretKey) {
|
|
|
97
102
|
return ab2hex(iv) + ab2hex(new Uint8Array(ciphertext));
|
|
98
103
|
}
|
|
99
104
|
/**
|
|
100
|
-
* Decrypts
|
|
101
|
-
*
|
|
102
|
-
* @param
|
|
103
|
-
* @
|
|
104
|
-
* @
|
|
105
|
-
*
|
|
106
|
-
* @throws {Error} If the secretKey is null or empty (via importKey).
|
|
107
|
-
* @throws {TypeError} If hex2ab receives an invalid input type.
|
|
105
|
+
* Decrypts a hexadecimal string (IV + ciphertext) using AES-CBC with a given secret key.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} encryptedValue - The concatenated hexadecimal string of the IV and ciphertext.
|
|
108
|
+
* @param {string} secretKey - The secret key to use for decryption.
|
|
109
|
+
* @returns {Promise<string>} A promise that resolves with the decrypted plain-text string.
|
|
110
|
+
* @throws {Error} If the encrypted value is null, empty, or too short, or if the `secretKey` is invalid.
|
|
108
111
|
*/
|
|
109
112
|
async function decrypt(encryptedValue, secretKey) {
|
|
110
113
|
if (!encryptedValue) {
|
|
@@ -130,55 +133,172 @@ async function decrypt(encryptedValue, secretKey) {
|
|
|
130
133
|
return new TextDecoder().decode(decryptedBuffer);
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new Error("Cannot access storage: window is not defined.");
|
|
136
|
+
const isServer = typeof window === 'undefined';
|
|
137
|
+
const isClient = typeof window !== 'undefined';
|
|
138
|
+
function getStorage() {
|
|
139
|
+
if (isServer) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return window.localStorage;
|
|
143
|
+
}
|
|
144
|
+
function getSessionStorage() {
|
|
145
|
+
if (isServer) {
|
|
146
|
+
return null;
|
|
145
147
|
}
|
|
146
|
-
|
|
148
|
+
return window.sessionStorage;
|
|
149
|
+
}
|
|
150
|
+
function getCookieStorage() {
|
|
151
|
+
return {
|
|
152
|
+
getItem: (key) => {
|
|
153
|
+
if (isServer)
|
|
154
|
+
return null;
|
|
155
|
+
const cookies = document.cookie.split(';');
|
|
156
|
+
for (const cookie of cookies) {
|
|
157
|
+
const [name, value] = cookie.trim().split('=');
|
|
158
|
+
if (name === key) {
|
|
159
|
+
return decodeURIComponent(value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
},
|
|
164
|
+
setItem: (key, value, options) => {
|
|
165
|
+
if (isServer)
|
|
166
|
+
return;
|
|
167
|
+
let cookie = `${key}=${encodeURIComponent(value)}`;
|
|
168
|
+
if (options?.expires) {
|
|
169
|
+
const date = new Date();
|
|
170
|
+
date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
|
|
171
|
+
cookie += `; expires=${date.toUTCString()}`;
|
|
172
|
+
}
|
|
173
|
+
cookie += `; path=${options?.path || '/'}`;
|
|
174
|
+
if (options?.domain) {
|
|
175
|
+
cookie += `; domain=${options.domain}`;
|
|
176
|
+
}
|
|
177
|
+
if (options?.secure !== false) {
|
|
178
|
+
const isSecure = options?.secure ?? (typeof window !== 'undefined' && window.location.protocol === 'https:');
|
|
179
|
+
if (isSecure) {
|
|
180
|
+
cookie += '; Secure';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (options?.sameSite) {
|
|
184
|
+
cookie += `; SameSite=${options.sameSite}`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
cookie += '; SameSite=Lax';
|
|
188
|
+
}
|
|
189
|
+
if (options?.httpOnly) {
|
|
190
|
+
console.warn('HttpOnly cookies cannot be set from JavaScript. Use server-side code to set HttpOnly cookies.');
|
|
191
|
+
}
|
|
192
|
+
document.cookie = cookie;
|
|
193
|
+
},
|
|
194
|
+
removeItem: (key, options) => {
|
|
195
|
+
if (isServer)
|
|
196
|
+
return;
|
|
197
|
+
const path = options?.path || '/';
|
|
198
|
+
const domain = options?.domain ? `; domain=${options.domain}` : '';
|
|
199
|
+
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}${domain}`;
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function getPreferredStorage() {
|
|
204
|
+
if (isServer) {
|
|
205
|
+
return getCookieStorage();
|
|
206
|
+
}
|
|
207
|
+
const storage = getStorage();
|
|
208
|
+
if (storage) {
|
|
209
|
+
return storage;
|
|
210
|
+
}
|
|
211
|
+
return getCookieStorage();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Encrypts and stores a key-value pair in either `localStorage`, `sessionStorage`, or cookies.
|
|
216
|
+
* Cookies are automatically used in SSR environments and can be explicitly requested.
|
|
217
|
+
* Cookies include security options: Secure (HTTPS only), SameSite (CSRF protection), and encryption.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} key - The key for the storage item.
|
|
220
|
+
* @param {string} value - The string value to encrypt and store.
|
|
221
|
+
* @param {string} secretKey - The secret key to use for encryption.
|
|
222
|
+
* @param {LocationPreference} location - The storage location: 'local' for `localStorage`, 'session' for `sessionStorage`, 'cookie' for cookies, or 'any' for retrieval.
|
|
223
|
+
* @param {CookieOptions} [cookieOptions] - Optional cookie-specific options (only used when location is 'cookie').
|
|
224
|
+
* @returns {Promise<void>} A promise that resolves when the item has been stored.
|
|
225
|
+
*/
|
|
226
|
+
async function storeEncryptedItem(key, value, secretKey, location, cookieOptions) {
|
|
147
227
|
const encryptedValue = await encrypt(value, secretKey);
|
|
148
|
-
|
|
228
|
+
if (location === "cookie" || isServer) {
|
|
229
|
+
const cookieStorage = getCookieStorage();
|
|
230
|
+
const defaultCookieOptions = {
|
|
231
|
+
expires: location === 'local' || isServer ? 365 : undefined,
|
|
232
|
+
path: '/',
|
|
233
|
+
secure: undefined,
|
|
234
|
+
sameSite: 'Lax',
|
|
235
|
+
...cookieOptions,
|
|
236
|
+
};
|
|
237
|
+
cookieStorage.setItem(key, encryptedValue, defaultCookieOptions);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const storage = location === "local" ? getStorage() : getSessionStorage();
|
|
241
|
+
if (storage) {
|
|
242
|
+
storage.setItem(key, encryptedValue);
|
|
243
|
+
}
|
|
149
244
|
}
|
|
150
245
|
/**
|
|
151
|
-
* Retrieves and decrypts
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* @param
|
|
156
|
-
* @
|
|
246
|
+
* Retrieves and decrypts an item from `localStorage`, `sessionStorage`, or cookies.
|
|
247
|
+
* When location is 'any', checks in order: sessionStorage, localStorage, cookies.
|
|
248
|
+
* Cookies are automatically checked in SSR environments.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} key - The key of the item to retrieve.
|
|
251
|
+
* @param {string} secretKey - The secret key to use for decryption.
|
|
252
|
+
* @param {LocationPreference} location - The storage location to search: 'local', 'session', 'cookie', or 'any' (checks session, local, cookie in that order).
|
|
253
|
+
* @returns {Promise<string | null>} A promise that resolves with the decrypted value, or `null` if the item is not found or decryption fails.
|
|
157
254
|
*/
|
|
158
255
|
async function getDecryptedItem(key, secretKey, location) {
|
|
159
|
-
if (typeof window === "undefined") {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
256
|
let encryptedData = null;
|
|
163
|
-
if (location === "
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
257
|
+
if (location === "cookie" || isServer) {
|
|
258
|
+
const cookieStorage = getCookieStorage();
|
|
259
|
+
encryptedData = cookieStorage.getItem(key);
|
|
260
|
+
if (encryptedData) {
|
|
261
|
+
try {
|
|
262
|
+
return await decrypt(encryptedData, secretKey);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (location === "cookie") {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
171
271
|
}
|
|
172
|
-
|
|
173
|
-
|
|
272
|
+
if (location === "session" || location === "any") {
|
|
273
|
+
const sessionStorage = getSessionStorage();
|
|
274
|
+
encryptedData = sessionStorage?.getItem(key) || null;
|
|
275
|
+
if (encryptedData) {
|
|
276
|
+
try {
|
|
277
|
+
return await decrypt(encryptedData, secretKey);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
174
283
|
}
|
|
175
|
-
|
|
176
|
-
|
|
284
|
+
if (location === "local" || location === "any") {
|
|
285
|
+
const storage = getStorage();
|
|
286
|
+
encryptedData = storage?.getItem(key) || null;
|
|
287
|
+
if (encryptedData) {
|
|
288
|
+
try {
|
|
289
|
+
return await decrypt(encryptedData, secretKey);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
177
295
|
}
|
|
296
|
+
return null;
|
|
178
297
|
}
|
|
179
298
|
|
|
180
299
|
/**
|
|
181
|
-
*
|
|
300
|
+
* An enum that defines a set of standardized screen size labels, ranging from extra-small (XS) to extra-extra-large (XXL).
|
|
301
|
+
* These labels are used to create a consistent vocabulary for responsive design across the application.
|
|
182
302
|
*/
|
|
183
303
|
var ScreenSize;
|
|
184
304
|
(function (ScreenSize) {
|
|
@@ -190,8 +310,8 @@ var ScreenSize;
|
|
|
190
310
|
ScreenSize["XXL"] = "XXL";
|
|
191
311
|
})(ScreenSize || (ScreenSize = {}));
|
|
192
312
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
313
|
+
* An enum that establishes specific pixel values for screen width breakpoints, corresponding to the labels in `ScreenSize`.
|
|
314
|
+
* These values are used to implement responsive design changes at standard device widths.
|
|
195
315
|
*/
|
|
196
316
|
var ScreenBreakpoint;
|
|
197
317
|
(function (ScreenBreakpoint) {
|
|
@@ -203,7 +323,9 @@ var ScreenBreakpoint;
|
|
|
203
323
|
ScreenBreakpoint[ScreenBreakpoint["XXL"] = 1600] = "XXL";
|
|
204
324
|
})(ScreenBreakpoint || (ScreenBreakpoint = {}));
|
|
205
325
|
/**
|
|
206
|
-
*
|
|
326
|
+
* A map that associates the symbolic screen size names from the `ScreenSize` enum with their
|
|
327
|
+
* corresponding pixel values from the `ScreenBreakpoint` enum. This provides an easy way to
|
|
328
|
+
* look up the pixel width for a given screen size label.
|
|
207
329
|
* @type {Map<ScreenSize, number>}
|
|
208
330
|
*/
|
|
209
331
|
const screenMap = new Map();
|
|
@@ -215,7 +337,9 @@ screenMap.set(ScreenSize.XL, ScreenBreakpoint.XL);
|
|
|
215
337
|
screenMap.set(ScreenSize.XXL, ScreenBreakpoint.XXL);
|
|
216
338
|
|
|
217
339
|
/**
|
|
218
|
-
*
|
|
340
|
+
* An enum that defines a comprehensive set of HTTP status codes, organized by category
|
|
341
|
+
* (Informational, Success, Redirection, Client Errors, Server Errors). It also includes
|
|
342
|
+
* custom error codes for application-specific scenarios like network errors.
|
|
219
343
|
* @readonly
|
|
220
344
|
*/
|
|
221
345
|
var ExceptionEnum;
|
|
@@ -294,7 +418,7 @@ var ExceptionEnum;
|
|
|
294
418
|
})(ExceptionEnum || (ExceptionEnum = {}));
|
|
295
419
|
|
|
296
420
|
/**
|
|
297
|
-
*
|
|
421
|
+
* An enum that defines MIME types for common image formats.
|
|
298
422
|
* @readonly
|
|
299
423
|
*/
|
|
300
424
|
var ImageTypes;
|
|
@@ -310,14 +434,26 @@ var ImageTypes;
|
|
|
310
434
|
ImageTypes["WEBP"] = "image/webp";
|
|
311
435
|
ImageTypes["XICON"] = "image/x-icon";
|
|
312
436
|
})(ImageTypes || (ImageTypes = {}));
|
|
437
|
+
/**
|
|
438
|
+
* An enum for generic audio MIME types.
|
|
439
|
+
* @readonly
|
|
440
|
+
*/
|
|
313
441
|
var AudioTypes;
|
|
314
442
|
(function (AudioTypes) {
|
|
315
443
|
AudioTypes["Audios"] = "audio/*";
|
|
316
444
|
})(AudioTypes || (AudioTypes = {}));
|
|
445
|
+
/**
|
|
446
|
+
* An enum for generic video MIME types.
|
|
447
|
+
* @readonly
|
|
448
|
+
*/
|
|
317
449
|
var VideoTypes;
|
|
318
450
|
(function (VideoTypes) {
|
|
319
451
|
VideoTypes["Videos"] = "video/*";
|
|
320
452
|
})(VideoTypes || (VideoTypes = {}));
|
|
453
|
+
/**
|
|
454
|
+
* An enum that defines MIME types for various text-based formats, including plain text, markup, and data serialization formats.
|
|
455
|
+
* @readonly
|
|
456
|
+
*/
|
|
321
457
|
var TextTypes;
|
|
322
458
|
(function (TextTypes) {
|
|
323
459
|
TextTypes["PlainText"] = "text/plain";
|
|
@@ -328,6 +464,10 @@ var TextTypes;
|
|
|
328
464
|
TextTypes["JSON"] = "application/json";
|
|
329
465
|
TextTypes["XML"] = "application/xml";
|
|
330
466
|
})(TextTypes || (TextTypes = {}));
|
|
467
|
+
/**
|
|
468
|
+
* An enum that defines MIME types for common document formats, such as PDFs and Microsoft Office files.
|
|
469
|
+
* @readonly
|
|
470
|
+
*/
|
|
331
471
|
var DocumentTypes;
|
|
332
472
|
(function (DocumentTypes) {
|
|
333
473
|
DocumentTypes["PDF"] = "application/pdf";
|
|
@@ -338,6 +478,10 @@ var DocumentTypes;
|
|
|
338
478
|
DocumentTypes["PowerPoint"] = "application/vnd.ms-powerpoint";
|
|
339
479
|
DocumentTypes["PowerPointOpenXML"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
340
480
|
})(DocumentTypes || (DocumentTypes = {}));
|
|
481
|
+
/**
|
|
482
|
+
* An enum that defines MIME types for popular archive and compression formats.
|
|
483
|
+
* @readonly
|
|
484
|
+
*/
|
|
341
485
|
var ArchiveTypes;
|
|
342
486
|
(function (ArchiveTypes) {
|
|
343
487
|
ArchiveTypes["ZIP"] = "application/zip";
|
|
@@ -345,6 +489,10 @@ var ArchiveTypes;
|
|
|
345
489
|
ArchiveTypes["TAR"] = "application/x-tar";
|
|
346
490
|
ArchiveTypes["RAR"] = "application/x-rar-compressed";
|
|
347
491
|
})(ArchiveTypes || (ArchiveTypes = {}));
|
|
492
|
+
/**
|
|
493
|
+
* An enum that defines MIME types for different font file formats.
|
|
494
|
+
* @readonly
|
|
495
|
+
*/
|
|
348
496
|
var FontTypes;
|
|
349
497
|
(function (FontTypes) {
|
|
350
498
|
FontTypes["TrueType"] = "font/ttf";
|
|
@@ -352,12 +500,20 @@ var FontTypes;
|
|
|
352
500
|
FontTypes["WebOpenFont"] = "font/woff";
|
|
353
501
|
FontTypes["WebOpenFont2"] = "font/woff2";
|
|
354
502
|
})(FontTypes || (FontTypes = {}));
|
|
503
|
+
/**
|
|
504
|
+
* An enum that defines MIME types for application-specific formats, including executables and packages.
|
|
505
|
+
* @readonly
|
|
506
|
+
*/
|
|
355
507
|
var AppTypes;
|
|
356
508
|
(function (AppTypes) {
|
|
357
509
|
AppTypes["Executable"] = "application/octet-stream";
|
|
358
510
|
AppTypes["AndroidAPK"] = "application/vnd.android.package-archive";
|
|
359
511
|
AppTypes["Java"] = "application/java-archive";
|
|
360
512
|
})(AppTypes || (AppTypes = {}));
|
|
513
|
+
/**
|
|
514
|
+
* An enum for miscellaneous MIME types that do not fit into the other categories.
|
|
515
|
+
* @readonly
|
|
516
|
+
*/
|
|
361
517
|
var OtherTypes;
|
|
362
518
|
(function (OtherTypes) {
|
|
363
519
|
OtherTypes["JSONLD"] = "application/ld+json";
|
|
@@ -423,10 +579,8 @@ var ContentTypeEnum;
|
|
|
423
579
|
})(ContentTypeEnum || (ContentTypeEnum = {}));
|
|
424
580
|
|
|
425
581
|
/**
|
|
426
|
-
*
|
|
427
|
-
* This
|
|
428
|
-
* The keys defined here correspond to specific key codes that can be used to detect
|
|
429
|
-
* user input in web applications.
|
|
582
|
+
* An enum that provides a comprehensive mapping of common keyboard keys to their corresponding key codes.
|
|
583
|
+
* This is useful for handling keyboard events in a readable and standardized way, avoiding the use of magic numbers.
|
|
430
584
|
* @readonly
|
|
431
585
|
* @enum {number}
|
|
432
586
|
*/
|
|
@@ -487,8 +641,8 @@ var KeyCodeEnum;
|
|
|
487
641
|
})(KeyCodeEnum || (KeyCodeEnum = {}));
|
|
488
642
|
|
|
489
643
|
/**
|
|
490
|
-
*
|
|
491
|
-
* This
|
|
644
|
+
* An enum that defines a standardized set of keys for accessing `localStorage` and `sessionStorage`.
|
|
645
|
+
* This helps to avoid magic strings and ensures consistency when managing data persistence in the browser.
|
|
492
646
|
* @readonly
|
|
493
647
|
* @enum {string}
|
|
494
648
|
*/
|
|
@@ -536,9 +690,9 @@ var StorageKeyEnum;
|
|
|
536
690
|
StorageKeyEnum["APP_SESSION_CACHE_KEY"] = "COMMON__SESSION__KEY";
|
|
537
691
|
})(StorageKeyEnum || (StorageKeyEnum = {}));
|
|
538
692
|
/**
|
|
539
|
-
*
|
|
693
|
+
* A frozen object that serves as an enum for storage types, distinguishing between `sessionStorage` and `localStorage`.
|
|
694
|
+
* Using this object provides a type-safe way to specify the desired storage mechanism.
|
|
540
695
|
* @readonly
|
|
541
|
-
* @enum {number}
|
|
542
696
|
*/
|
|
543
697
|
const StorageTypeEnum = {
|
|
544
698
|
/**
|
|
@@ -554,7 +708,9 @@ const StorageTypeEnum = {
|
|
|
554
708
|
};
|
|
555
709
|
|
|
556
710
|
/**
|
|
557
|
-
*
|
|
711
|
+
* An enum that defines a vocabulary of error types, covering a wide range of potential issues
|
|
712
|
+
* from validation and network problems to runtime and syntax errors. This provides a standardized
|
|
713
|
+
* way to classify errors throughout the application.
|
|
558
714
|
* @readonly
|
|
559
715
|
*/
|
|
560
716
|
var ErrorEnum;
|
|
@@ -575,28 +731,31 @@ var ErrorEnum;
|
|
|
575
731
|
ErrorEnum["URI"] = "uri";
|
|
576
732
|
})(ErrorEnum || (ErrorEnum = {}));
|
|
577
733
|
/**
|
|
578
|
-
*
|
|
734
|
+
* An enum that provides standardized, user-friendly messages for different error types.
|
|
735
|
+
* These messages are intended to be displayed to the user or used in logs.
|
|
579
736
|
* @readonly
|
|
580
737
|
*/
|
|
581
738
|
var ErrorMessages;
|
|
582
739
|
(function (ErrorMessages) {
|
|
583
|
-
ErrorMessages["WARNING"] = "
|
|
584
|
-
ErrorMessages["ERROR"] = "
|
|
585
|
-
ErrorMessages["CRITICAL"] = "
|
|
586
|
-
ErrorMessages["VALIDATION"] = "
|
|
587
|
-
ErrorMessages["COMPONENT"] = "Error
|
|
588
|
-
ErrorMessages["NETWORK"] = "
|
|
589
|
-
ErrorMessages["AUTHENTICATION"] = "
|
|
590
|
-
ErrorMessages["RUNTIME"] = "
|
|
591
|
-
ErrorMessages["TYPE"] = "
|
|
592
|
-
ErrorMessages["REFERENCE"] = "
|
|
593
|
-
ErrorMessages["SYNTAX"] = "
|
|
594
|
-
ErrorMessages["RANGE"] = "Error
|
|
595
|
-
ErrorMessages["EVAL"] = "Error
|
|
596
|
-
ErrorMessages["URI"] = "URI
|
|
740
|
+
ErrorMessages["WARNING"] = "Something is not quite right. Please check and try again.";
|
|
741
|
+
ErrorMessages["ERROR"] = "An unexpected error occurred.";
|
|
742
|
+
ErrorMessages["CRITICAL"] = "Critical error. Please contact technical support.";
|
|
743
|
+
ErrorMessages["VALIDATION"] = "There are errors in the form. Please review the fields.";
|
|
744
|
+
ErrorMessages["COMPONENT"] = "Error loading the component. Try reloading.";
|
|
745
|
+
ErrorMessages["NETWORK"] = "Connection problem. Please check your internet.";
|
|
746
|
+
ErrorMessages["AUTHENTICATION"] = "You do not have permission to perform this action.";
|
|
747
|
+
ErrorMessages["RUNTIME"] = "Execution error. Please try again.";
|
|
748
|
+
ErrorMessages["TYPE"] = "Unidentified error type.";
|
|
749
|
+
ErrorMessages["REFERENCE"] = "Invalid reference.";
|
|
750
|
+
ErrorMessages["SYNTAX"] = "Syntax error in the code.";
|
|
751
|
+
ErrorMessages["RANGE"] = "Error in the range of values.";
|
|
752
|
+
ErrorMessages["EVAL"] = "Error in evaluation.";
|
|
753
|
+
ErrorMessages["URI"] = "Invalid URI.";
|
|
597
754
|
})(ErrorMessages || (ErrorMessages = {}));
|
|
598
755
|
/**
|
|
599
|
-
*
|
|
756
|
+
* An enum that defines CSS styles for console log messages, corresponding to each error type in `ErrorEnum`.
|
|
757
|
+
* This allows for visually distinct, styled logging in the browser's console, making it easier to
|
|
758
|
+
* identify the severity and type of logged errors during development.
|
|
600
759
|
* @readonly
|
|
601
760
|
*/
|
|
602
761
|
var ErrorStyles;
|
|
@@ -617,7 +776,8 @@ var ErrorStyles;
|
|
|
617
776
|
ErrorStyles["URI"] = "color: darkcyan; font-weight: bold;";
|
|
618
777
|
})(ErrorStyles || (ErrorStyles = {}));
|
|
619
778
|
/**
|
|
620
|
-
* A record
|
|
779
|
+
* A frozen record that maps each `ErrorEnum` member to its corresponding user-friendly message from `ErrorMessages`.
|
|
780
|
+
* This provides a convenient, type-safe way to look up error messages.
|
|
621
781
|
*/
|
|
622
782
|
const ERROR_MESSAGES = {
|
|
623
783
|
[ErrorEnum.WARNING]: ErrorMessages.WARNING,
|
|
@@ -636,7 +796,8 @@ const ERROR_MESSAGES = {
|
|
|
636
796
|
[ErrorEnum.URI]: ErrorMessages.URI,
|
|
637
797
|
};
|
|
638
798
|
/**
|
|
639
|
-
* A record
|
|
799
|
+
* A frozen record that maps each `ErrorEnum` member to its corresponding CSS style string from `ErrorStyles`.
|
|
800
|
+
* This enables easy retrieval of the correct style for console logging based on the error type.
|
|
640
801
|
*/
|
|
641
802
|
const ERROR_STYLES = {
|
|
642
803
|
[ErrorEnum.WARNING]: ErrorStyles.WARNING,
|
|
@@ -655,42 +816,202 @@ const ERROR_STYLES = {
|
|
|
655
816
|
[ErrorEnum.URI]: ErrorStyles.URI,
|
|
656
817
|
};
|
|
657
818
|
|
|
819
|
+
class BaseError extends Error {
|
|
820
|
+
timestamp;
|
|
821
|
+
context;
|
|
822
|
+
constructor(message, context) {
|
|
823
|
+
super(message);
|
|
824
|
+
this.name = this.constructor.name;
|
|
825
|
+
this.timestamp = new Date();
|
|
826
|
+
this.context = context;
|
|
827
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
828
|
+
}
|
|
829
|
+
toJSON() {
|
|
830
|
+
return {
|
|
831
|
+
name: this.name,
|
|
832
|
+
message: this.message,
|
|
833
|
+
code: this.code,
|
|
834
|
+
statusCode: this.statusCode,
|
|
835
|
+
timestamp: this.timestamp.toISOString(),
|
|
836
|
+
context: this.context,
|
|
837
|
+
stack: this.stack,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
class NetworkError extends BaseError {
|
|
843
|
+
code = 'NETWORK_ERROR';
|
|
844
|
+
statusCode;
|
|
845
|
+
originalError;
|
|
846
|
+
constructor(message = 'Network request failed', statusCode, originalError, context) {
|
|
847
|
+
super(message, context);
|
|
848
|
+
this.statusCode = statusCode;
|
|
849
|
+
this.originalError = originalError;
|
|
850
|
+
}
|
|
851
|
+
static fromAxiosError(error) {
|
|
852
|
+
const statusCode = error.response?.status;
|
|
853
|
+
const message = error.response?.data?.message || error.message || 'Network request failed';
|
|
854
|
+
return new NetworkError(message, statusCode, error, {
|
|
855
|
+
url: error.config?.url,
|
|
856
|
+
method: error.config?.method,
|
|
857
|
+
responseData: error.response?.data,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
static fromFetchError(error) {
|
|
861
|
+
return new NetworkError(error.message || 'Network request failed', undefined, error, {
|
|
862
|
+
cause: error.cause,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
class AuthError extends BaseError {
|
|
868
|
+
code = 'AUTH_ERROR';
|
|
869
|
+
statusCode = 401;
|
|
870
|
+
constructor(message = 'Authentication failed', context) {
|
|
871
|
+
super(message, context);
|
|
872
|
+
}
|
|
873
|
+
static unauthorized(message) {
|
|
874
|
+
return new AuthError(message || 'Unauthorized access');
|
|
875
|
+
}
|
|
876
|
+
static tokenExpired() {
|
|
877
|
+
return new AuthError('Token has expired');
|
|
878
|
+
}
|
|
879
|
+
static tokenInvalid() {
|
|
880
|
+
return new AuthError('Invalid token');
|
|
881
|
+
}
|
|
882
|
+
static tokenMissing() {
|
|
883
|
+
return new AuthError('Authentication token is missing');
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
class ValidationError extends BaseError {
|
|
888
|
+
code = 'VALIDATION_ERROR';
|
|
889
|
+
statusCode = 422;
|
|
890
|
+
issues;
|
|
891
|
+
constructor(message = 'Validation failed', issues = [], context) {
|
|
892
|
+
super(message, context);
|
|
893
|
+
this.issues = issues;
|
|
894
|
+
}
|
|
895
|
+
static fromIssues(issues) {
|
|
896
|
+
return new ValidationError(`Validation failed: ${issues.length} issue(s)`, issues);
|
|
897
|
+
}
|
|
898
|
+
static fromField(field, message, value) {
|
|
899
|
+
return new ValidationError(`Validation failed for field "${field}"`, [{ field, message, value }]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
class ServerError extends BaseError {
|
|
904
|
+
code = 'SERVER_ERROR';
|
|
905
|
+
statusCode;
|
|
906
|
+
constructor(message = 'Server error occurred', statusCode = 500, context) {
|
|
907
|
+
super(message, context);
|
|
908
|
+
this.statusCode = statusCode;
|
|
909
|
+
}
|
|
910
|
+
static internal(message) {
|
|
911
|
+
return new ServerError(message || 'Internal server error', 500);
|
|
912
|
+
}
|
|
913
|
+
static badGateway(message) {
|
|
914
|
+
return new ServerError(message || 'Bad gateway', 502);
|
|
915
|
+
}
|
|
916
|
+
static serviceUnavailable(message) {
|
|
917
|
+
return new ServerError(message || 'Service unavailable', 503);
|
|
918
|
+
}
|
|
919
|
+
static gatewayTimeout(message) {
|
|
920
|
+
return new ServerError(message || 'Gateway timeout', 504);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
658
924
|
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* @param
|
|
664
|
-
* @returns
|
|
665
|
-
|
|
666
|
-
|
|
925
|
+
* Handles and logs errors to the console. Supports both standard Error objects and custom error classes
|
|
926
|
+
* (BaseError, NetworkError, etc.). It infers the error type, logs a styled message with detailed information,
|
|
927
|
+
* and returns structured error information for further handling.
|
|
928
|
+
*
|
|
929
|
+
* @param {unknown} error - The captured error, which can be a string, Error object, or custom error class.
|
|
930
|
+
* @returns {ErrorInfo | undefined} An object containing the error message, type, and additional error data,
|
|
931
|
+
* or `undefined` if the error is not an Error instance.
|
|
932
|
+
*
|
|
933
|
+
* @example
|
|
934
|
+
* ```typescript
|
|
935
|
+
* try {
|
|
936
|
+
* await someOperation();
|
|
937
|
+
* } catch (error) {
|
|
938
|
+
* const errorInfo = handleError(error);
|
|
939
|
+
* if (errorInfo) {
|
|
940
|
+
* // Use errorInfo.message, errorInfo.type, errorInfo.errorData
|
|
941
|
+
* // Handle redirect or other actions as needed
|
|
942
|
+
* }
|
|
943
|
+
* }
|
|
944
|
+
* ```
|
|
945
|
+
*/
|
|
946
|
+
function handleError(error) {
|
|
667
947
|
if (!(error instanceof Error)) {
|
|
668
948
|
console.error("Unrecognized error:", error);
|
|
669
|
-
return;
|
|
949
|
+
return undefined;
|
|
670
950
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
951
|
+
let type;
|
|
952
|
+
let message;
|
|
953
|
+
let errorData = undefined;
|
|
954
|
+
if (error instanceof BaseError) {
|
|
955
|
+
type = inferErrorTypeFromBaseError(error);
|
|
956
|
+
message = error.message || ERROR_MESSAGES[type] || 'Error desconocido.';
|
|
957
|
+
errorData = {
|
|
958
|
+
code: error.code,
|
|
959
|
+
statusCode: error.statusCode,
|
|
960
|
+
context: error.context,
|
|
961
|
+
timestamp: error.timestamp,
|
|
962
|
+
};
|
|
963
|
+
if (error instanceof ValidationError && error.issues.length > 0) {
|
|
964
|
+
errorData.issues = error.issues;
|
|
678
965
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
type = inferErrorType(error);
|
|
969
|
+
message = ERROR_MESSAGES[type] || error.message || 'Error desconocido.';
|
|
970
|
+
}
|
|
971
|
+
const logMessage = `%c[${type.toUpperCase()}] ${message}`;
|
|
972
|
+
const logStyle = ERROR_STYLES[type] || ERROR_STYLES.error;
|
|
973
|
+
console.group(logMessage, logStyle);
|
|
974
|
+
console.error('Error details:', error);
|
|
975
|
+
if (errorData) {
|
|
976
|
+
console.error('Error data:', errorData);
|
|
977
|
+
}
|
|
978
|
+
if (error.stack) {
|
|
979
|
+
console.error('Stack trace:', error.stack);
|
|
980
|
+
}
|
|
981
|
+
console.groupEnd();
|
|
982
|
+
return {
|
|
983
|
+
message,
|
|
984
|
+
type,
|
|
985
|
+
...(errorData && { errorData }),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function inferErrorTypeFromBaseError(error) {
|
|
989
|
+
if (error instanceof NetworkError) {
|
|
990
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
991
|
+
return 'authentication';
|
|
682
992
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
993
|
+
return 'network';
|
|
994
|
+
}
|
|
995
|
+
if (error instanceof AuthError) {
|
|
996
|
+
return 'authentication';
|
|
997
|
+
}
|
|
998
|
+
if (error instanceof ValidationError) {
|
|
999
|
+
return 'validation';
|
|
1000
|
+
}
|
|
1001
|
+
if (error instanceof ServerError) {
|
|
1002
|
+
if (error.statusCode >= 500) {
|
|
1003
|
+
return 'critical';
|
|
686
1004
|
}
|
|
1005
|
+
return 'error';
|
|
687
1006
|
}
|
|
688
|
-
return
|
|
1007
|
+
return 'error';
|
|
689
1008
|
}
|
|
690
1009
|
/**
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
1010
|
+
* Infers the `ErrorType` of an error by analyzing its name and message content.
|
|
1011
|
+
* This helps in categorizing the error for standardized handling and logging.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {string | Error} error - The error to analyze.
|
|
1014
|
+
* @returns {ErrorType} The inferred error type.
|
|
694
1015
|
*/
|
|
695
1016
|
function inferErrorType(error) {
|
|
696
1017
|
const msg = typeof error === 'string' ? error : error.message;
|
|
@@ -715,15 +1036,14 @@ let endpointsConfig = {
|
|
|
715
1036
|
LOGOUT: "/logout",
|
|
716
1037
|
};
|
|
717
1038
|
/**
|
|
718
|
-
* Configures authentication endpoint URLs
|
|
719
|
-
* This function
|
|
720
|
-
*
|
|
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.
|
|
1039
|
+
* Configures the global authentication endpoint URLs for the application.
|
|
1040
|
+
* This function should be called once at startup to define the API endpoints for login,
|
|
1041
|
+
* token refresh, and logout. The configuration is then frozen to prevent changes.
|
|
725
1042
|
*
|
|
726
|
-
* @
|
|
1043
|
+
* @param {EndpointConfig} config - An object containing the URLs for the authentication endpoints.
|
|
1044
|
+
* @param {string} config.loginEndpoint - The URL for the login endpoint.
|
|
1045
|
+
* @param {string} config.refreshEndpoint - The URL for the token refresh endpoint.
|
|
1046
|
+
* @param {string} config.logoutEndpoint - The URL for the logout endpoint.
|
|
727
1047
|
*/
|
|
728
1048
|
function configEndpoints(config) {
|
|
729
1049
|
endpointsConfig = Object.freeze({
|
|
@@ -733,12 +1053,9 @@ function configEndpoints(config) {
|
|
|
733
1053
|
});
|
|
734
1054
|
}
|
|
735
1055
|
/**
|
|
736
|
-
* Retrieves the configured authentication endpoint URLs.
|
|
1056
|
+
* Retrieves the globally configured authentication endpoint URLs.
|
|
737
1057
|
*
|
|
738
|
-
* @returns {EndpointsConfig}
|
|
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.
|
|
1058
|
+
* @returns {EndpointsConfig} A frozen object containing the configured `LOGIN`, `REFRESH`, and `LOGOUT` endpoints.
|
|
742
1059
|
*/
|
|
743
1060
|
function getEndpointsConfig() {
|
|
744
1061
|
return endpointsConfig;
|
|
@@ -746,13 +1063,11 @@ function getEndpointsConfig() {
|
|
|
746
1063
|
|
|
747
1064
|
let appKey = null;
|
|
748
1065
|
/**
|
|
749
|
-
* Sets the main application encryption
|
|
750
|
-
* This
|
|
1066
|
+
* Sets the main application key, which is intended for use in encryption and signing operations.
|
|
1067
|
+
* This function should be called once at application startup to configure the key.
|
|
751
1068
|
*
|
|
752
|
-
* @param {AppKeyConfig} config - An object containing the application
|
|
753
|
-
* @param {string} config.
|
|
754
|
-
*
|
|
755
|
-
* @returns {void} Does not return anything, but updates the application key.
|
|
1069
|
+
* @param {AppKeyConfig} config - An object containing the application key.
|
|
1070
|
+
* @param {string} config.appKey - The application key.
|
|
756
1071
|
* @throws {Error} If the provided key is null, undefined, or an empty string.
|
|
757
1072
|
*/
|
|
758
1073
|
function configAppKey(config) {
|
|
@@ -762,11 +1077,10 @@ function configAppKey(config) {
|
|
|
762
1077
|
appKey = config.appKey;
|
|
763
1078
|
}
|
|
764
1079
|
/**
|
|
765
|
-
* Retrieves the
|
|
766
|
-
* Throws an error if the application key has not been configured.
|
|
1080
|
+
* Retrieves the configured application key.
|
|
767
1081
|
*
|
|
768
|
-
* @returns {string} The configured application
|
|
769
|
-
* @throws {Error} If the application
|
|
1082
|
+
* @returns {string} The configured application key.
|
|
1083
|
+
* @throws {Error} If the application key has not been set by calling `configAppKey` first.
|
|
770
1084
|
*/
|
|
771
1085
|
function getAppKey() {
|
|
772
1086
|
if (appKey === null) {
|
|
@@ -830,15 +1144,16 @@ async function loadSessionConfig() {
|
|
|
830
1144
|
}
|
|
831
1145
|
}
|
|
832
1146
|
/**
|
|
833
|
-
* Configures the session
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
* before applying changes and then saving them.
|
|
1147
|
+
* Configures the session ID and persistence preference for the application.
|
|
1148
|
+
* This function allows setting a custom session ID and specifying whether session-related
|
|
1149
|
+
* data should be stored in `localStorage` or `sessionStorage`. The configuration is
|
|
1150
|
+
* then encrypted and saved to the chosen storage.
|
|
838
1151
|
*
|
|
839
|
-
* @param {SessionConfigObject} config - An object containing the
|
|
840
|
-
* the
|
|
841
|
-
* @
|
|
1152
|
+
* @param {SessionConfigObject} config - An object containing the session configuration.
|
|
1153
|
+
* @param {string} [config.sessionId] - A unique identifier for the session. If not provided, the existing one is maintained.
|
|
1154
|
+
* @param {LocationPreference} [config.persistencePreference] - The storage location ('local' or 'session').
|
|
1155
|
+
* If not provided, the existing preference is maintained.
|
|
1156
|
+
* @returns {Promise<void>} A promise that resolves once the session has been configured and saved.
|
|
842
1157
|
*/
|
|
843
1158
|
async function configSession(config) {
|
|
844
1159
|
if (config.sessionId) {
|
|
@@ -851,30 +1166,27 @@ async function configSession(config) {
|
|
|
851
1166
|
await saveSessionConfig();
|
|
852
1167
|
}
|
|
853
1168
|
/**
|
|
854
|
-
* Retrieves the current session identifier.
|
|
855
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1169
|
+
* Retrieves the current session identifier, loading it from storage if available.
|
|
856
1170
|
*
|
|
857
|
-
* @returns {Promise<string>} A promise that resolves with the
|
|
1171
|
+
* @returns {Promise<string>} A promise that resolves with the session ID.
|
|
858
1172
|
*/
|
|
859
1173
|
async function getSessionId() {
|
|
860
1174
|
await loadSessionConfig();
|
|
861
1175
|
return _sessionConfig.SESSION_ID;
|
|
862
1176
|
}
|
|
863
1177
|
/**
|
|
864
|
-
* Retrieves the current data persistence preference.
|
|
865
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1178
|
+
* Retrieves the current data persistence preference, loading it from storage if available.
|
|
866
1179
|
*
|
|
867
|
-
* @returns {Promise<
|
|
1180
|
+
* @returns {Promise<LocationPreference>} A promise that resolves with the persistence preference ('local' or 'session').
|
|
868
1181
|
*/
|
|
869
1182
|
async function getSessionPersistence() {
|
|
870
1183
|
await loadSessionConfig();
|
|
871
1184
|
return _sessionConfig.PERSISTENCE;
|
|
872
1185
|
}
|
|
873
1186
|
/**
|
|
874
|
-
* Retrieves the complete session configuration.
|
|
875
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1187
|
+
* Retrieves the complete session configuration object, loading it from storage if available.
|
|
876
1188
|
*
|
|
877
|
-
* @returns {Promise<SessionConfig>} A promise that resolves with the session configuration
|
|
1189
|
+
* @returns {Promise<SessionConfig>} A promise that resolves with the full session configuration.
|
|
878
1190
|
*/
|
|
879
1191
|
async function getSessionConfig() {
|
|
880
1192
|
await loadSessionConfig();
|
|
@@ -890,14 +1202,12 @@ let refreshTokenPathsConfig = {
|
|
|
890
1202
|
refreshTokenPath: "data.refresh_token",
|
|
891
1203
|
};
|
|
892
1204
|
/**
|
|
893
|
-
* Configures the paths for access and refresh tokens
|
|
894
|
-
*
|
|
1205
|
+
* Configures the dot-notation paths for extracting access and refresh tokens from the initial login response.
|
|
1206
|
+
* The configuration is frozen to prevent runtime changes.
|
|
895
1207
|
*
|
|
896
|
-
* @param {TokenPathsConfig} config - An object containing the paths
|
|
897
|
-
* @param {string} [config.accessTokenPath="data.access_token"] -
|
|
898
|
-
* @param {string} [config.refreshTokenPath="data.refresh_token"] -
|
|
899
|
-
*
|
|
900
|
-
* @returns {void} Does not return anything but freezes the token paths configuration object.
|
|
1208
|
+
* @param {TokenPathsConfig} config - An object containing the token paths.
|
|
1209
|
+
* @param {string} [config.accessTokenPath="data.access_token"] - The path to the access token.
|
|
1210
|
+
* @param {string} [config.refreshTokenPath="data.refresh_token"] - The path to the refresh token.
|
|
901
1211
|
*/
|
|
902
1212
|
function configTokenPaths(config) {
|
|
903
1213
|
tokenPathsConfig = Object.freeze({
|
|
@@ -906,14 +1216,12 @@ function configTokenPaths(config) {
|
|
|
906
1216
|
});
|
|
907
1217
|
}
|
|
908
1218
|
/**
|
|
909
|
-
* Configures the paths for access and refresh tokens
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* @param {TokenPathsConfig} config - An object containing the paths for access and refresh tokens.
|
|
913
|
-
* @param {string} [config.accessTokenPath="data.access_token"] - Path to the access token in the response.
|
|
914
|
-
* @param {string} [config.refreshTokenPath="data.refresh_token"] - Path to the refresh token in the response.
|
|
1219
|
+
* Configures the dot-notation paths for extracting access and refresh tokens from the token refresh response.
|
|
1220
|
+
* The configuration is frozen to prevent runtime changes.
|
|
915
1221
|
*
|
|
916
|
-
* @
|
|
1222
|
+
* @param {TokenPathsConfig} config - An object containing the token paths for the refresh response.
|
|
1223
|
+
* @param {string} [config.accessTokenPath="data.access_token"] - The path to the new access token.
|
|
1224
|
+
* @param {string} [config.refreshTokenPath="data.refresh_token"] - The path to the new refresh token.
|
|
917
1225
|
*/
|
|
918
1226
|
function configRefreshTokenPaths(config) {
|
|
919
1227
|
refreshTokenPathsConfig = Object.freeze({
|
|
@@ -922,135 +1230,139 @@ function configRefreshTokenPaths(config) {
|
|
|
922
1230
|
});
|
|
923
1231
|
}
|
|
924
1232
|
/**
|
|
925
|
-
* Retrieves the configured
|
|
1233
|
+
* Retrieves the configured token paths for the initial login response.
|
|
926
1234
|
*
|
|
927
|
-
* @returns {AuthTokenPaths}
|
|
928
|
-
* @property {string} accessTokenPath - Path to the access token in the response.
|
|
929
|
-
* @property {string} refreshTokenPath - Path to the refresh token in the response.
|
|
1235
|
+
* @returns {AuthTokenPaths} A frozen object containing the `accessTokenPath` and `refreshTokenPath`.
|
|
930
1236
|
*/
|
|
931
1237
|
function getTokenPathsConfig() {
|
|
932
1238
|
return tokenPathsConfig;
|
|
933
1239
|
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Retrieves the configured token paths for the token refresh response.
|
|
1242
|
+
*
|
|
1243
|
+
* @returns {AuthTokenPaths} A frozen object containing the `accessTokenPath` and `refreshTokenPath` for the refresh response.
|
|
1244
|
+
*/
|
|
934
1245
|
function getRefreshTokenPathsConfig() {
|
|
935
1246
|
return refreshTokenPathsConfig;
|
|
936
1247
|
}
|
|
937
1248
|
|
|
938
1249
|
/**
|
|
939
|
-
*
|
|
940
|
-
* from either sessionStorage, localStorage, or both based on the provided location preference.
|
|
1250
|
+
* Removes all stored authentication credentials (access and refresh tokens) from the specified storage locations.
|
|
941
1251
|
*
|
|
942
|
-
* @param {LocationPreference} location - The storage
|
|
943
|
-
*
|
|
1252
|
+
* @param {LocationPreference} location - The storage location to clear. Can be 'local' for `localStorage`,
|
|
1253
|
+
* 'session' for `sessionStorage`, 'cookie' for cookies, or 'any' to clear all.
|
|
1254
|
+
* @returns {Promise<void>} A promise that resolves when the credentials have been cleared.
|
|
944
1255
|
*/
|
|
945
1256
|
const cleanCredentials = async (location) => {
|
|
946
1257
|
const tokensConfig = getTokenConfig();
|
|
1258
|
+
if (location === "cookie" || isServer) {
|
|
1259
|
+
const cookieStorage = getCookieStorage();
|
|
1260
|
+
Object.keys(tokensConfig).forEach((key) => {
|
|
1261
|
+
cookieStorage.removeItem(tokensConfig[key], { path: '/' });
|
|
1262
|
+
});
|
|
1263
|
+
if (location === "cookie") {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
947
1267
|
Object.keys(tokensConfig).forEach((key) => {
|
|
948
1268
|
const itemKey = tokensConfig[key];
|
|
949
1269
|
if (location === "local" || location === "any") {
|
|
950
|
-
|
|
1270
|
+
const storage = getStorage();
|
|
1271
|
+
storage?.removeItem(itemKey);
|
|
951
1272
|
}
|
|
952
1273
|
if (location === "session" || location === "any") {
|
|
953
|
-
sessionStorage
|
|
1274
|
+
const sessionStorage = getSessionStorage();
|
|
1275
|
+
sessionStorage?.removeItem(itemKey);
|
|
954
1276
|
}
|
|
955
1277
|
});
|
|
956
1278
|
};
|
|
957
1279
|
/**
|
|
958
|
-
* Retrieves
|
|
959
|
-
* using the provided secret key and based on the specified session preference.
|
|
1280
|
+
* Retrieves and decrypts the access token from the specified storage location.
|
|
960
1281
|
*
|
|
961
|
-
* @param {string} secretKey - The secret key
|
|
962
|
-
* @param {
|
|
963
|
-
* @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
|
|
1282
|
+
* @param {string} secretKey - The secret key to use for decryption.
|
|
1283
|
+
* @param {LocationPreference} location - The storage location to search ('local', 'session', 'cookie', or 'any').
|
|
1284
|
+
* @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or `null` if it's not found.
|
|
964
1285
|
*/
|
|
965
1286
|
const getAuthToken = async (secretKey, location) => {
|
|
966
1287
|
const tokensConfig = getTokenConfig();
|
|
967
1288
|
return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
|
|
968
1289
|
};
|
|
969
1290
|
/**
|
|
970
|
-
* Retrieves the
|
|
971
|
-
* using the provided secret key and based on the specified session preference.
|
|
1291
|
+
* Retrieves and decrypts the refresh token from the specified storage location.
|
|
972
1292
|
*
|
|
973
|
-
* @param {string} secretKey - The secret key
|
|
974
|
-
* @param {
|
|
975
|
-
* @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
|
|
1293
|
+
* @param {string} secretKey - The secret key to use for decryption.
|
|
1294
|
+
* @param {LocationPreference} location - The storage location to search ('local', 'session', 'cookie', or 'any').
|
|
1295
|
+
* @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or `null` if it's not found.
|
|
976
1296
|
*/
|
|
977
1297
|
const getAuthRefreshToken = async (secretKey, location) => {
|
|
978
1298
|
const tokensConfig = getTokenConfig();
|
|
979
1299
|
return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
|
|
980
1300
|
};
|
|
981
1301
|
/**
|
|
982
|
-
*
|
|
983
|
-
* based on the specified session preference.
|
|
1302
|
+
* Encrypts and stores the access token in the specified storage location.
|
|
984
1303
|
*
|
|
985
1304
|
* @param {string} token - The access token to store.
|
|
986
|
-
* @param {string} secretKey - The secret key
|
|
987
|
-
* @param {
|
|
988
|
-
* @returns {Promise<void>} A promise that resolves when the token
|
|
1305
|
+
* @param {string} secretKey - The secret key to use for encryption.
|
|
1306
|
+
* @param {LocationPreference} location - The storage location ('local', 'session', or 'cookie').
|
|
1307
|
+
* @returns {Promise<void>} A promise that resolves when the token has been stored.
|
|
989
1308
|
*/
|
|
990
1309
|
const storeAuthToken = async (token, secretKey, location) => {
|
|
991
1310
|
const tokensConfig = getTokenConfig();
|
|
992
1311
|
await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
|
|
993
1312
|
};
|
|
994
1313
|
/**
|
|
995
|
-
*
|
|
996
|
-
* based on the specified session preference.
|
|
1314
|
+
* Encrypts and stores the refresh token in the specified storage location.
|
|
997
1315
|
*
|
|
998
1316
|
* @param {string} token - The refresh token to store.
|
|
999
|
-
* @param {string} secretKey - The secret key
|
|
1000
|
-
* @param {
|
|
1001
|
-
* @returns {Promise<void>} A promise that resolves when the token
|
|
1317
|
+
* @param {string} secretKey - The secret key to use for encryption.
|
|
1318
|
+
* @param {LocationPreference} location - The storage location ('local', 'session', or 'cookie').
|
|
1319
|
+
* @returns {Promise<void>} A promise that resolves when the token has been stored.
|
|
1002
1320
|
*/
|
|
1003
1321
|
const storeAuthRefreshToken = async (token, secretKey, location) => {
|
|
1004
1322
|
const tokensConfig = getTokenConfig();
|
|
1005
1323
|
await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
|
|
1006
1324
|
};
|
|
1007
1325
|
/**
|
|
1008
|
-
* Verifies the
|
|
1009
|
-
*
|
|
1326
|
+
* Verifies the current user's authentication status by checking for a valid, unexpired access token.
|
|
1327
|
+
* It searches for the token in all storage locations (sessionStorage, localStorage, cookies).
|
|
1328
|
+
* If the token is missing, malformed, or expired, it logs the issue, clears credentials, and returns `false`.
|
|
1010
1329
|
*
|
|
1011
|
-
* @returns {Promise<boolean>}
|
|
1012
|
-
* @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
|
|
1013
|
-
* "TOKEN_INVALID" if the token format is invalid.
|
|
1330
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the user is authenticated, and `false` otherwise.
|
|
1014
1331
|
*/
|
|
1015
1332
|
const verifyAuth = async () => {
|
|
1016
1333
|
const sessionPersistence = 'any';
|
|
1017
1334
|
const handleAuthError = async (message, shouldClean = true) => {
|
|
1018
|
-
handleError(message
|
|
1335
|
+
handleError(message);
|
|
1019
1336
|
if (shouldClean) {
|
|
1020
1337
|
await cleanCredentials(sessionPersistence);
|
|
1021
1338
|
}
|
|
1022
1339
|
return false;
|
|
1023
1340
|
};
|
|
1341
|
+
const token = await getAuthToken(getAppKey(), sessionPersistence);
|
|
1342
|
+
if (!token) {
|
|
1343
|
+
return handleAuthError("TOKEN_MISSING: No valid token found");
|
|
1344
|
+
}
|
|
1024
1345
|
try {
|
|
1025
|
-
const
|
|
1026
|
-
if (!token) {
|
|
1027
|
-
return await handleAuthError("TOKEN_MISSING: No valid token found");
|
|
1028
|
-
}
|
|
1029
|
-
let decoded;
|
|
1030
|
-
try {
|
|
1031
|
-
decoded = jwtDecode(token);
|
|
1032
|
-
}
|
|
1033
|
-
catch (decodeError) {
|
|
1034
|
-
return await handleAuthError("TOKEN_INVALID: Invalid token format");
|
|
1035
|
-
}
|
|
1346
|
+
const decoded = jwtDecode(token);
|
|
1036
1347
|
const currentTime = Date.now() / 1000;
|
|
1037
1348
|
if (typeof decoded.exp !== "number") {
|
|
1038
|
-
return
|
|
1349
|
+
return handleAuthError("TOKEN_INVALID: Invalid expiration format");
|
|
1039
1350
|
}
|
|
1040
1351
|
if (decoded.exp <= currentTime) {
|
|
1041
|
-
return
|
|
1352
|
+
return handleAuthError("TOKEN_EXPIRED: Token is expired");
|
|
1042
1353
|
}
|
|
1043
1354
|
return true;
|
|
1044
1355
|
}
|
|
1045
1356
|
catch (error) {
|
|
1046
|
-
return
|
|
1357
|
+
return handleAuthError("TOKEN_INVALID: Invalid token format");
|
|
1047
1358
|
}
|
|
1048
1359
|
};
|
|
1049
1360
|
|
|
1050
1361
|
/**
|
|
1051
|
-
* Converts a Proxy object
|
|
1052
|
-
*
|
|
1053
|
-
* @
|
|
1362
|
+
* Converts a Proxy object into a plain JavaScript object.
|
|
1363
|
+
*
|
|
1364
|
+
* @param {ProxyConstructor} proxy - The Proxy object to convert.
|
|
1365
|
+
* @returns {any} A new object containing the properties of the Proxy.
|
|
1054
1366
|
*/
|
|
1055
1367
|
function proxyToPlainObject(proxy) {
|
|
1056
1368
|
if (!proxy)
|
|
@@ -1062,10 +1374,11 @@ function proxyToPlainObject(proxy) {
|
|
|
1062
1374
|
return plainObject;
|
|
1063
1375
|
}
|
|
1064
1376
|
/**
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
* @param {
|
|
1068
|
-
* @
|
|
1377
|
+
* Performs a shallow comparison to check if two objects have the same keys.
|
|
1378
|
+
*
|
|
1379
|
+
* @param {Record<string, any>} object1 - The first object.
|
|
1380
|
+
* @param {Record<string, any>} object2 - The second object.
|
|
1381
|
+
* @returns {boolean} `true` if both objects have the exact same set of keys, otherwise `false`.
|
|
1069
1382
|
*/
|
|
1070
1383
|
function compareObject(object1, object2) {
|
|
1071
1384
|
return Object.keys(object1).every(function (element) {
|
|
@@ -1073,10 +1386,11 @@ function compareObject(object1, object2) {
|
|
|
1073
1386
|
});
|
|
1074
1387
|
}
|
|
1075
1388
|
/**
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
* @param {
|
|
1079
|
-
* @
|
|
1389
|
+
* Performs a deep comparison between two objects to determine if they are structurally and value-wise equal.
|
|
1390
|
+
*
|
|
1391
|
+
* @param {Record<string, any>} object1 - The first object.
|
|
1392
|
+
* @param {Record<string, any>} object2 - The second object.
|
|
1393
|
+
* @returns {boolean} `true` if the objects are deeply equal, otherwise `false`.
|
|
1080
1394
|
*/
|
|
1081
1395
|
function deepEqual(object1, object2) {
|
|
1082
1396
|
if (object1 === object2)
|
|
@@ -1097,9 +1411,12 @@ function deepEqual(object1, object2) {
|
|
|
1097
1411
|
return true;
|
|
1098
1412
|
}
|
|
1099
1413
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
* @
|
|
1414
|
+
* Creates a deep clone of a given object, including nested objects and arrays.
|
|
1415
|
+
*
|
|
1416
|
+
* @template T - The type of the object being cloned.
|
|
1417
|
+
* @param {T} obj - The object to clone.
|
|
1418
|
+
* @returns {T} A new object that is a deep clone of the original.
|
|
1419
|
+
* @throws {Error} If the object contains a type that cannot be cloned.
|
|
1103
1420
|
*/
|
|
1104
1421
|
function deepClone(obj) {
|
|
1105
1422
|
if (obj === null || typeof obj !== 'object') {
|
|
@@ -1123,9 +1440,10 @@ function deepClone(obj) {
|
|
|
1123
1440
|
throw new Error('Unable to clone object! Its type is not supported.');
|
|
1124
1441
|
}
|
|
1125
1442
|
/**
|
|
1126
|
-
* Converts
|
|
1127
|
-
*
|
|
1128
|
-
* @
|
|
1443
|
+
* Converts a flat object into a URL query string.
|
|
1444
|
+
*
|
|
1445
|
+
* @param {Record<string, any>} obj - The object to convert.
|
|
1446
|
+
* @returns {string} The resulting URL query string.
|
|
1129
1447
|
*/
|
|
1130
1448
|
function objectToQueryString(obj) {
|
|
1131
1449
|
return Object.keys(obj)
|
|
@@ -1135,10 +1453,11 @@ function objectToQueryString(obj) {
|
|
|
1135
1453
|
// Example usage:
|
|
1136
1454
|
// objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
|
|
1137
1455
|
/**
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
* @param {
|
|
1141
|
-
* @
|
|
1456
|
+
* Compares two objects and returns an object containing the keys where their values differ.
|
|
1457
|
+
*
|
|
1458
|
+
* @param {Record<string, any>} object1 - The first object.
|
|
1459
|
+
* @param {Record<string, any>} object2 - The second object.
|
|
1460
|
+
* @returns {Record<string, any>} An object where each key represents a difference, and the value contains the differing values from both objects.
|
|
1142
1461
|
*/
|
|
1143
1462
|
function getObjectDifferences(object1, object2) {
|
|
1144
1463
|
const differences = {};
|
|
@@ -1151,10 +1470,11 @@ function getObjectDifferences(object1, object2) {
|
|
|
1151
1470
|
return differences;
|
|
1152
1471
|
}
|
|
1153
1472
|
/**
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* @param {
|
|
1157
|
-
* @
|
|
1473
|
+
* Creates a new object containing only the specified keys from the original object.
|
|
1474
|
+
*
|
|
1475
|
+
* @param {Record<string, any>} obj - The source object.
|
|
1476
|
+
* @param {string[]} keys - An array of keys to include in the new object.
|
|
1477
|
+
* @returns {Record<string, any>} A new object with the filtered properties.
|
|
1158
1478
|
*/
|
|
1159
1479
|
function filterObjectByKeys(obj, keys) {
|
|
1160
1480
|
const filteredObject = {};
|
|
@@ -1168,10 +1488,12 @@ function filterObjectByKeys(obj, keys) {
|
|
|
1168
1488
|
// Example usage:
|
|
1169
1489
|
// filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
|
|
1170
1490
|
/**
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1173
|
-
* @
|
|
1174
|
-
* @
|
|
1491
|
+
* Recursively merges the properties of a source object into a target object.
|
|
1492
|
+
*
|
|
1493
|
+
* @template T - The type of the target object.
|
|
1494
|
+
* @param {T} target - The object to merge properties into.
|
|
1495
|
+
* @param {Partial<T>} source - The object from which to merge properties.
|
|
1496
|
+
* @returns {T} The modified target object.
|
|
1175
1497
|
*/
|
|
1176
1498
|
function deepMerge(target, source) {
|
|
1177
1499
|
if (target === null || typeof target !== 'object' || typeof source !== 'object') {
|
|
@@ -1193,18 +1515,20 @@ function deepMerge(target, source) {
|
|
|
1193
1515
|
return target;
|
|
1194
1516
|
}
|
|
1195
1517
|
/**
|
|
1196
|
-
* Checks if an object
|
|
1197
|
-
*
|
|
1198
|
-
* @
|
|
1518
|
+
* Checks if an object has no own enumerable properties.
|
|
1519
|
+
*
|
|
1520
|
+
* @param {Record<string, any>} obj - The object to check.
|
|
1521
|
+
* @returns {boolean} `true` if the object is empty, otherwise `false`.
|
|
1199
1522
|
*/
|
|
1200
1523
|
function isEmptyObject(obj) {
|
|
1201
1524
|
return Object.keys(obj).length === 0;
|
|
1202
1525
|
}
|
|
1203
1526
|
/**
|
|
1204
|
-
* Safely
|
|
1205
|
-
*
|
|
1206
|
-
* @param {
|
|
1207
|
-
* @
|
|
1527
|
+
* Safely retrieves a nested property from an object using an array of keys as the path.
|
|
1528
|
+
*
|
|
1529
|
+
* @param {Record<string, any>} obj - The object to query.
|
|
1530
|
+
* @param {string[]} keys - An array of keys representing the path to the nested property.
|
|
1531
|
+
* @returns {*} The value of the nested property, or `undefined` if the path is not valid.
|
|
1208
1532
|
*/
|
|
1209
1533
|
function safeGet(obj, keys) {
|
|
1210
1534
|
return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
|
|
@@ -1213,9 +1537,10 @@ function safeGet(obj, keys) {
|
|
|
1213
1537
|
// safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
|
|
1214
1538
|
// safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
|
|
1215
1539
|
/**
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
* @
|
|
1540
|
+
* Creates a new object with all properties that have `null`, `undefined`, or empty string values removed.
|
|
1541
|
+
*
|
|
1542
|
+
* @param {Record<string, any>} obj - The source object.
|
|
1543
|
+
* @returns {Record<string, any>} A new object containing only the non-empty properties.
|
|
1219
1544
|
*/
|
|
1220
1545
|
function removeEmptyProperties(obj) {
|
|
1221
1546
|
return Object.keys(obj)
|
|
@@ -1228,9 +1553,10 @@ function removeEmptyProperties(obj) {
|
|
|
1228
1553
|
// Example usage:
|
|
1229
1554
|
// removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
|
|
1230
1555
|
/**
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
* @
|
|
1556
|
+
* Returns an array of an object's own enumerable property names.
|
|
1557
|
+
*
|
|
1558
|
+
* @param {Record<string, any>} obj - The object to get the keys from.
|
|
1559
|
+
* @returns {string[]} An array of string keys.
|
|
1234
1560
|
*/
|
|
1235
1561
|
function getObjectKeys(obj) {
|
|
1236
1562
|
return Object.keys(obj);
|
|
@@ -1238,9 +1564,10 @@ function getObjectKeys(obj) {
|
|
|
1238
1564
|
// Example usage:
|
|
1239
1565
|
// getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
|
|
1240
1566
|
/**
|
|
1241
|
-
* Checks if
|
|
1242
|
-
*
|
|
1243
|
-
* @
|
|
1567
|
+
* Checks if any of the object's properties are themselves objects (and not null).
|
|
1568
|
+
*
|
|
1569
|
+
* @param {Record<string, any>} obj - The object to inspect.
|
|
1570
|
+
* @returns {boolean} `true` if the object contains at least one nested object, otherwise `false`.
|
|
1244
1571
|
*/
|
|
1245
1572
|
function hasNestedProperties(obj) {
|
|
1246
1573
|
return Object.values(obj).some(value => typeof value === 'object' && value !== null);
|
|
@@ -1249,11 +1576,12 @@ function hasNestedProperties(obj) {
|
|
|
1249
1576
|
// hasNestedProperties({ a: 1, b: { c: 2 } }); // true
|
|
1250
1577
|
// hasNestedProperties({ a: 1, b: 2 }); // false
|
|
1251
1578
|
/**
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
* @param {
|
|
1255
|
-
* @param {
|
|
1256
|
-
* @
|
|
1579
|
+
* Recursively converts a nested object into a `FormData` object.
|
|
1580
|
+
*
|
|
1581
|
+
* @param {Record<string, any>} obj - The object to convert.
|
|
1582
|
+
* @param {FormData} [formData=new FormData()] - An existing `FormData` object to append to.
|
|
1583
|
+
* @param {string} [parentKey=''] - The base key for nested properties.
|
|
1584
|
+
* @returns {FormData} The resulting `FormData` object.
|
|
1257
1585
|
*/
|
|
1258
1586
|
function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
|
|
1259
1587
|
Object.entries(obj).forEach(([key, value]) => {
|
|
@@ -1270,12 +1598,12 @@ function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = ''
|
|
|
1270
1598
|
// Example usage:
|
|
1271
1599
|
// objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
|
|
1272
1600
|
/**
|
|
1273
|
-
*
|
|
1601
|
+
* Recursively converts a JavaScript object into a `FormData` object, handling nested objects and boolean conversion.
|
|
1274
1602
|
*
|
|
1275
|
-
* @param obj - The object to
|
|
1276
|
-
* @param form - An optional FormData
|
|
1277
|
-
* @param namespace - An optional namespace
|
|
1278
|
-
* @returns
|
|
1603
|
+
* @param {any} obj - The object to convert.
|
|
1604
|
+
* @param {FormData} [form] - An optional existing `FormData` object to append to.
|
|
1605
|
+
* @param {string} [namespace] - An optional namespace for keys of nested properties.
|
|
1606
|
+
* @returns {FormData} The resulting `FormData` object.
|
|
1279
1607
|
*/
|
|
1280
1608
|
const objectToFormData = function (obj, form, namespace) {
|
|
1281
1609
|
const fd = form || new FormData();
|
|
@@ -1305,11 +1633,12 @@ const objectToFormData = function (obj, form, namespace) {
|
|
|
1305
1633
|
return fd;
|
|
1306
1634
|
};
|
|
1307
1635
|
/**
|
|
1308
|
-
* Flattens a nested object
|
|
1309
|
-
*
|
|
1310
|
-
* @param {string}
|
|
1311
|
-
* @param {
|
|
1312
|
-
* @
|
|
1636
|
+
* Flattens a nested object into a single-level object with dot-separated keys.
|
|
1637
|
+
*
|
|
1638
|
+
* @param {Record<string, any>} obj - The object to flatten.
|
|
1639
|
+
* @param {string} [parentKey=''] - The prefix to use for the keys of the flattened properties.
|
|
1640
|
+
* @param {Record<string, any>} [result={}] - An object to merge the flattened properties into.
|
|
1641
|
+
* @returns {Record<string, any>} The flattened object.
|
|
1313
1642
|
*/
|
|
1314
1643
|
function flattenObject(obj, parentKey = '', result = {}) {
|
|
1315
1644
|
for (const key in obj) {
|
|
@@ -1329,13 +1658,14 @@ function flattenObject(obj, parentKey = '', result = {}) {
|
|
|
1329
1658
|
// flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
|
|
1330
1659
|
|
|
1331
1660
|
/**
|
|
1332
|
-
* Extracts
|
|
1333
|
-
*
|
|
1661
|
+
* Extracts access and refresh tokens from a response object using specified dot-notation paths
|
|
1662
|
+
* and validates their existence and type.
|
|
1334
1663
|
*
|
|
1335
|
-
* @param {any} data - The
|
|
1336
|
-
* @param {AuthTokenPaths} tokenPaths -
|
|
1337
|
-
* @param {string} errorSource - A
|
|
1338
|
-
* @returns {TokenValidationResult} An object
|
|
1664
|
+
* @param {any} data - The response object from which to extract the tokens.
|
|
1665
|
+
* @param {AuthTokenPaths} tokenPaths - An object containing the dot-notation paths for the access and refresh tokens.
|
|
1666
|
+
* @param {string} errorSource - A string to identify the source of the operation (e.g., "LOGIN", "REFRESH") for error messages.
|
|
1667
|
+
* @returns {TokenValidationResult} An object containing the extracted `accessToken` and `refreshToken`.
|
|
1668
|
+
* @throws {Error} If the data object is missing, or if the access or refresh tokens cannot be found at the specified paths or are not strings.
|
|
1339
1669
|
*/
|
|
1340
1670
|
const extractAndValidateTokens = (data, tokenPaths, errorSource) => {
|
|
1341
1671
|
const accessTokenPath = tokenPaths?.accessTokenPath || "access_token";
|
|
@@ -1355,52 +1685,149 @@ const extractAndValidateTokens = (data, tokenPaths, errorSource) => {
|
|
|
1355
1685
|
};
|
|
1356
1686
|
|
|
1357
1687
|
/**
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
*
|
|
1361
|
-
* @param {string}
|
|
1362
|
-
* @param {
|
|
1363
|
-
|
|
1688
|
+
* Encrypts and stores both the access and refresh tokens in the specified storage location.
|
|
1689
|
+
* Supports localStorage, sessionStorage, and cookies (with encryption and security options).
|
|
1690
|
+
*
|
|
1691
|
+
* @param {string} accessToken - The access token to be stored.
|
|
1692
|
+
* @param {string} refreshToken - The refresh token to be stored.
|
|
1693
|
+
* @param {LocationPreference} persistence - The desired storage location: 'local' for `localStorage`, 'session' for `sessionStorage`, or 'cookie' for cookies.
|
|
1694
|
+
* @returns {Promise<void>} A promise that resolves when both tokens have been successfully stored.
|
|
1695
|
+
*/
|
|
1364
1696
|
const storeTokens = async (accessToken, refreshToken, persistence) => {
|
|
1365
1697
|
await storeAuthToken(accessToken, getAppKey(), persistence);
|
|
1366
1698
|
await storeAuthRefreshToken(refreshToken, getAppKey(), persistence);
|
|
1367
1699
|
};
|
|
1368
1700
|
|
|
1701
|
+
let defaultAuthFetcher = null;
|
|
1702
|
+
let createDefaultFetcher = null;
|
|
1703
|
+
/**
|
|
1704
|
+
* Configures a default fetcher for authentication operations.
|
|
1705
|
+
* If not configured, useAuth will use the default Axios fetcher.
|
|
1706
|
+
*
|
|
1707
|
+
* @param fetcher - The fetcher function to use for auth operations
|
|
1708
|
+
*
|
|
1709
|
+
* @example
|
|
1710
|
+
* ```typescript
|
|
1711
|
+
* import { configAuthFetcher, createOfetchFetcher } from '@arex95/vue-core';
|
|
1712
|
+
*
|
|
1713
|
+
* const ofetchFetcher = createOfetchFetcher();
|
|
1714
|
+
* configAuthFetcher(ofetchFetcher);
|
|
1715
|
+
* ```
|
|
1716
|
+
*/
|
|
1717
|
+
function configAuthFetcher(fetcher) {
|
|
1718
|
+
defaultAuthFetcher = fetcher;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Configures a factory function to create the default fetcher lazily.
|
|
1722
|
+
* This is used internally to avoid circular dependencies.
|
|
1723
|
+
*
|
|
1724
|
+
* @param factory - Factory function that creates a fetcher
|
|
1725
|
+
*/
|
|
1726
|
+
function setDefaultAuthFetcherFactory(factory) {
|
|
1727
|
+
createDefaultFetcher = factory;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Gets the default auth fetcher, creating one from Axios if not configured.
|
|
1731
|
+
* This allows lazy initialization to avoid circular dependencies.
|
|
1732
|
+
*
|
|
1733
|
+
* @returns The fetcher function to use
|
|
1734
|
+
*/
|
|
1735
|
+
function getDefaultAuthFetcher() {
|
|
1736
|
+
if (defaultAuthFetcher) {
|
|
1737
|
+
return defaultAuthFetcher;
|
|
1738
|
+
}
|
|
1739
|
+
if (createDefaultFetcher) {
|
|
1740
|
+
defaultAuthFetcher = createDefaultFetcher();
|
|
1741
|
+
return defaultAuthFetcher;
|
|
1742
|
+
}
|
|
1743
|
+
throw new Error('Auth fetcher not configured. Please configure Axios or set a custom auth fetcher.');
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1369
1746
|
/**
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
const refreshTokens = async (
|
|
1747
|
+
* Refreshes the access and refresh tokens by making a POST request to the refresh endpoint.
|
|
1748
|
+
* It retrieves the current refresh token from storage, sends it to the refresh endpoint,
|
|
1749
|
+
* and then stores the new tokens upon a successful response. If the refresh process fails
|
|
1750
|
+
* or no refresh token is found, it clears all credentials and reloads the page.
|
|
1751
|
+
*
|
|
1752
|
+
* @param {Fetcher} [fetcher] - Optional fetcher function to use for the refresh request. If not provided, uses the default configured fetcher.
|
|
1753
|
+
* @returns {Promise<AuthResponse>} A promise that resolves with the new authentication response containing the refreshed tokens.
|
|
1754
|
+
* @throws {Error} Throws an error if the refresh token is missing or if the refresh request fails, which is then caught to trigger a logout.
|
|
1755
|
+
*/
|
|
1756
|
+
const refreshTokens = async (fetcher) => {
|
|
1380
1757
|
const tokenPaths = getRefreshTokenPathsConfig();
|
|
1381
1758
|
const endpoints = getEndpointsConfig();
|
|
1382
1759
|
const secretKey = getAppKey();
|
|
1383
1760
|
const persistence = await getSessionPersistence();
|
|
1761
|
+
const getFetcher = () => fetcher || getDefaultAuthFetcher();
|
|
1384
1762
|
try {
|
|
1385
1763
|
const refreshTokenFromStorage = await getAuthRefreshToken(secretKey, "any");
|
|
1386
1764
|
if (!refreshTokenFromStorage) {
|
|
1387
1765
|
throw new Error("TOKEN_MISSING: No refresh token found in storage.");
|
|
1388
1766
|
}
|
|
1389
|
-
const
|
|
1767
|
+
const data = await getFetcher()({
|
|
1768
|
+
method: 'POST',
|
|
1769
|
+
url: endpoints.REFRESH,
|
|
1770
|
+
});
|
|
1390
1771
|
const { accessToken, refreshToken } = extractAndValidateTokens(data, tokenPaths, "REFRESH");
|
|
1391
1772
|
await storeTokens(accessToken, refreshToken, persistence);
|
|
1392
1773
|
return data;
|
|
1393
1774
|
}
|
|
1394
1775
|
catch (error) {
|
|
1395
|
-
handleError(error
|
|
1776
|
+
handleError(error);
|
|
1396
1777
|
await cleanCredentials(persistence);
|
|
1397
|
-
if (window) {
|
|
1778
|
+
if (typeof window !== 'undefined') {
|
|
1398
1779
|
window.location.reload();
|
|
1399
1780
|
}
|
|
1400
1781
|
throw error;
|
|
1401
1782
|
}
|
|
1402
1783
|
};
|
|
1403
1784
|
|
|
1785
|
+
/**
|
|
1786
|
+
* Creates a fetcher function using Axios.
|
|
1787
|
+
*
|
|
1788
|
+
* @param axiosInstance - The Axios instance to use
|
|
1789
|
+
* @returns A fetcher function compatible with RestStd
|
|
1790
|
+
*
|
|
1791
|
+
* @example
|
|
1792
|
+
* ```typescript
|
|
1793
|
+
* import axios from 'axios';
|
|
1794
|
+
* import { createAxiosFetcher, RestStd } from '@arex95/vue-core';
|
|
1795
|
+
*
|
|
1796
|
+
* const axiosInstance = axios.create({ baseURL: 'https://api.example.com' });
|
|
1797
|
+
*
|
|
1798
|
+
* export class Role extends RestStd {
|
|
1799
|
+
* static override resource = 'roles';
|
|
1800
|
+
* static fetchFn = createAxiosFetcher(axiosInstance);
|
|
1801
|
+
* }
|
|
1802
|
+
* ```
|
|
1803
|
+
*/
|
|
1804
|
+
function createAxiosFetcher(axiosInstance) {
|
|
1805
|
+
return async (config) => {
|
|
1806
|
+
try {
|
|
1807
|
+
const response = await axiosInstance({
|
|
1808
|
+
method: config.method,
|
|
1809
|
+
url: config.url,
|
|
1810
|
+
params: config.params,
|
|
1811
|
+
data: config.data,
|
|
1812
|
+
headers: config.headers,
|
|
1813
|
+
});
|
|
1814
|
+
return response.data;
|
|
1815
|
+
}
|
|
1816
|
+
catch (error) {
|
|
1817
|
+
if (error instanceof AxiosError || error?.isAxiosError) {
|
|
1818
|
+
throw NetworkError.fromAxiosError(error);
|
|
1819
|
+
}
|
|
1820
|
+
throw error;
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* A service class that encapsulates a customizable Axios instance with built-in interceptors
|
|
1827
|
+
* for handling authentication, token refreshing, and request cancellation. It is designed to
|
|
1828
|
+
* streamline API communication by automatically attaching authorization headers and managing
|
|
1829
|
+
* token refresh logic for 401 Unauthorized responses.
|
|
1830
|
+
*/
|
|
1404
1831
|
class AxiosService {
|
|
1405
1832
|
instance;
|
|
1406
1833
|
cancelTokenSource;
|
|
@@ -1408,6 +1835,10 @@ class AxiosService {
|
|
|
1408
1835
|
refreshTokenUrl;
|
|
1409
1836
|
isRefreshing = false;
|
|
1410
1837
|
failedQueue = [];
|
|
1838
|
+
/**
|
|
1839
|
+
* Creates an instance of AxiosService.
|
|
1840
|
+
* @param {AxiosServiceOptions} options - Configuration options for the Axios instance, such as `baseURL`, `timeout`, and custom `headers`.
|
|
1841
|
+
*/
|
|
1411
1842
|
constructor(options) {
|
|
1412
1843
|
this.cancelTokenSource = axios.CancelToken.source();
|
|
1413
1844
|
const endpointsConfig = getEndpointsConfig();
|
|
@@ -1450,7 +1881,7 @@ class AxiosService {
|
|
|
1450
1881
|
this.activeRequests++;
|
|
1451
1882
|
return config;
|
|
1452
1883
|
}, (error) => {
|
|
1453
|
-
handleError(error
|
|
1884
|
+
handleError(error);
|
|
1454
1885
|
return Promise.reject(error);
|
|
1455
1886
|
});
|
|
1456
1887
|
this.instance.interceptors.response.use((response) => {
|
|
@@ -1463,11 +1894,11 @@ class AxiosService {
|
|
|
1463
1894
|
const isRefreshCall = originalRequest?.url === this.refreshTokenUrl;
|
|
1464
1895
|
const isRetry = originalRequest?._retry === true;
|
|
1465
1896
|
if (!isAuthError || isRefreshCall || isRetry) {
|
|
1466
|
-
handleError(error
|
|
1897
|
+
handleError(error);
|
|
1467
1898
|
return Promise.reject(error);
|
|
1468
1899
|
}
|
|
1469
1900
|
if (!originalRequest) {
|
|
1470
|
-
handleError(error
|
|
1901
|
+
handleError(error);
|
|
1471
1902
|
return Promise.reject(error);
|
|
1472
1903
|
}
|
|
1473
1904
|
if (this.isRefreshing) {
|
|
@@ -1485,7 +1916,8 @@ class AxiosService {
|
|
|
1485
1916
|
this.isRefreshing = true;
|
|
1486
1917
|
originalRequest._retry = true;
|
|
1487
1918
|
try {
|
|
1488
|
-
|
|
1919
|
+
const fetcher = createAxiosFetcher(this.instance);
|
|
1920
|
+
await refreshTokens(fetcher);
|
|
1489
1921
|
const newToken = await getAuthToken(getAppKey(), "any");
|
|
1490
1922
|
if (newToken) {
|
|
1491
1923
|
this.processQueue(null, newToken);
|
|
@@ -1502,49 +1934,111 @@ class AxiosService {
|
|
|
1502
1934
|
catch (refreshError) {
|
|
1503
1935
|
this.processQueue(refreshError, null);
|
|
1504
1936
|
this.isRefreshing = false;
|
|
1505
|
-
handleError(refreshError
|
|
1937
|
+
handleError(refreshError);
|
|
1506
1938
|
return Promise.reject(error);
|
|
1507
1939
|
}
|
|
1508
1940
|
});
|
|
1509
1941
|
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Returns the number of active (in-flight) requests.
|
|
1944
|
+
* @returns {number} The number of active requests.
|
|
1945
|
+
*/
|
|
1510
1946
|
getActiveRequests() {
|
|
1511
1947
|
return this.activeRequests;
|
|
1512
1948
|
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Returns the underlying Axios instance.
|
|
1951
|
+
* @returns {AxiosInstance} The Axios instance.
|
|
1952
|
+
*/
|
|
1513
1953
|
getAxiosInstance() {
|
|
1514
1954
|
return this.instance;
|
|
1515
1955
|
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Cancels all ongoing requests made by this Axios instance.
|
|
1958
|
+
*/
|
|
1516
1959
|
cancelAllRequests() {
|
|
1517
1960
|
this.cancelTokenSource.cancel("Operation canceled by the user.");
|
|
1518
1961
|
this.cancelTokenSource = axios.CancelToken.source();
|
|
1519
1962
|
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Sets a default header for all subsequent requests.
|
|
1965
|
+
* @param {string} key - The header key.
|
|
1966
|
+
* @param {string} value - The header value.
|
|
1967
|
+
*/
|
|
1520
1968
|
setHeader(key, value) {
|
|
1521
1969
|
this.instance.defaults.headers.common[key] = value;
|
|
1522
1970
|
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Removes a default header.
|
|
1973
|
+
* @param {string} key - The header key to remove.
|
|
1974
|
+
*/
|
|
1523
1975
|
removeHeader(key) {
|
|
1524
1976
|
delete this.instance.defaults.headers.common[key];
|
|
1525
1977
|
}
|
|
1526
1978
|
}
|
|
1527
1979
|
|
|
1528
|
-
let axiosServiceInstance;
|
|
1980
|
+
let axiosServiceInstance = null;
|
|
1981
|
+
let defaultConfig = null;
|
|
1982
|
+
/**
|
|
1983
|
+
* Configures the singleton Axios service instance for the application.
|
|
1984
|
+
* This function should be called once at the application's entry point to set up
|
|
1985
|
+
* the base URL, headers, and other default configurations for all API requests.
|
|
1986
|
+
*
|
|
1987
|
+
* @param {AxiosServiceOptions} config - The configuration options for the Axios service.
|
|
1988
|
+
*/
|
|
1529
1989
|
const configAxios = (config) => {
|
|
1990
|
+
defaultConfig = config;
|
|
1530
1991
|
axiosServiceInstance = new AxiosService({
|
|
1531
1992
|
baseURL: config.baseURL,
|
|
1532
1993
|
headers: config.headers,
|
|
1533
1994
|
timeout: config.timeout,
|
|
1534
1995
|
withCredentials: config.withCredentials
|
|
1535
1996
|
});
|
|
1997
|
+
// Configure auth fetcher factory lazily to avoid circular dependency
|
|
1998
|
+
// The factory function is only called when getDefaultAuthFetcher() is invoked
|
|
1999
|
+
setDefaultAuthFetcherFactory(() => {
|
|
2000
|
+
return createAxiosFetcher(axiosServiceInstance.getAxiosInstance());
|
|
2001
|
+
});
|
|
1536
2002
|
};
|
|
2003
|
+
/**
|
|
2004
|
+
* Retrieves the configured singleton Axios instance.
|
|
2005
|
+
* If not configured yet, creates a default instance with minimal configuration.
|
|
2006
|
+
* This allows lazy initialization to avoid dependency circular issues in Nuxt and other frameworks.
|
|
2007
|
+
*
|
|
2008
|
+
* @returns {AxiosInstance} The configured Axios instance.
|
|
2009
|
+
*/
|
|
1537
2010
|
const getConfiguredAxiosInstance = () => {
|
|
1538
2011
|
if (!axiosServiceInstance) {
|
|
1539
|
-
|
|
2012
|
+
if (defaultConfig) {
|
|
2013
|
+
axiosServiceInstance = new AxiosService({
|
|
2014
|
+
baseURL: defaultConfig.baseURL,
|
|
2015
|
+
headers: defaultConfig.headers,
|
|
2016
|
+
timeout: defaultConfig.timeout,
|
|
2017
|
+
withCredentials: defaultConfig.withCredentials
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
else {
|
|
2021
|
+
axiosServiceInstance = new AxiosService({
|
|
2022
|
+
baseURL: '',
|
|
2023
|
+
headers: {},
|
|
2024
|
+
timeout: 30000,
|
|
2025
|
+
withCredentials: false
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
// Configure auth fetcher factory lazily to avoid circular dependency
|
|
2029
|
+
// The factory function is only called when getDefaultAuthFetcher() is invoked
|
|
2030
|
+
setDefaultAuthFetcherFactory(() => {
|
|
2031
|
+
return createAxiosFetcher(axiosServiceInstance.getAxiosInstance());
|
|
2032
|
+
});
|
|
1540
2033
|
}
|
|
1541
2034
|
return axiosServiceInstance.getAxiosInstance();
|
|
1542
2035
|
};
|
|
1543
2036
|
|
|
1544
2037
|
/**
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
* @param {
|
|
2038
|
+
* Triggers a browser download for a file created from a `Blob` object.
|
|
2039
|
+
*
|
|
2040
|
+
* @param {Blob} blob - The `Blob` containing the file's data.
|
|
2041
|
+
* @param {string} fileName - The desired name for the downloaded file.
|
|
1548
2042
|
*/
|
|
1549
2043
|
function downloadFile(blob, fileName) {
|
|
1550
2044
|
const link = document.createElement('a');
|
|
@@ -1559,10 +2053,11 @@ function downloadFile(blob, fileName) {
|
|
|
1559
2053
|
URL.revokeObjectURL(url);
|
|
1560
2054
|
}
|
|
1561
2055
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
* @param {
|
|
1565
|
-
* @param {
|
|
2056
|
+
* Converts an array of data into a CSV format and triggers a download.
|
|
2057
|
+
*
|
|
2058
|
+
* @param {string[]} headers - An array of strings to be used as the CSV header row.
|
|
2059
|
+
* @param {any[][]} data - A 2D array representing the rows and cells of the data to be exported.
|
|
2060
|
+
* @param {string} fileName - The desired name for the downloaded CSV file.
|
|
1566
2061
|
*/
|
|
1567
2062
|
function exportToCSV(headers, data, fileName) {
|
|
1568
2063
|
const csvRows = [];
|
|
@@ -1579,10 +2074,11 @@ function exportToCSV(headers, data, fileName) {
|
|
|
1579
2074
|
downloadFile(blob, fileName);
|
|
1580
2075
|
}
|
|
1581
2076
|
/**
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1584
|
-
* @param {
|
|
1585
|
-
* @param {
|
|
2077
|
+
* Converts an array of data into an HTML table, then triggers a download as an Excel (.xls) file.
|
|
2078
|
+
*
|
|
2079
|
+
* @param {string[]} headers - An array of strings for the table headers.
|
|
2080
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2081
|
+
* @param {string} fileName - The desired name for the downloaded Excel file.
|
|
1586
2082
|
*/
|
|
1587
2083
|
function exportToExcel(headers, data, fileName) {
|
|
1588
2084
|
// Create a table element
|
|
@@ -1611,9 +2107,10 @@ function exportToExcel(headers, data, fileName) {
|
|
|
1611
2107
|
downloadFile(blob, fileName);
|
|
1612
2108
|
}
|
|
1613
2109
|
/**
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
* @param {
|
|
2110
|
+
* Converts an array of data into a pretty-printed JSON string and triggers a download.
|
|
2111
|
+
*
|
|
2112
|
+
* @param {any[]} data - The data to be serialized into JSON.
|
|
2113
|
+
* @param {string} fileName - The desired name for the downloaded JSON file.
|
|
1617
2114
|
*/
|
|
1618
2115
|
function exportToJSON(data, fileName) {
|
|
1619
2116
|
const jsonContent = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
|
|
@@ -1622,10 +2119,11 @@ function exportToJSON(data, fileName) {
|
|
|
1622
2119
|
downloadFile(blob, fileName);
|
|
1623
2120
|
}
|
|
1624
2121
|
/**
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
* @param {
|
|
1628
|
-
* @param {
|
|
2122
|
+
* Converts an array of data into a simple XML format and triggers a download.
|
|
2123
|
+
*
|
|
2124
|
+
* @param {string[]} headers - An array of strings to be used as column headers in the XML.
|
|
2125
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2126
|
+
* @param {string} fileName - The desired name for the downloaded XML file.
|
|
1629
2127
|
*/
|
|
1630
2128
|
function exportToXML(headers, data, fileName) {
|
|
1631
2129
|
let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<rows>\n';
|
|
@@ -1649,10 +2147,11 @@ function exportToXML(headers, data, fileName) {
|
|
|
1649
2147
|
downloadFile(blob, fileName);
|
|
1650
2148
|
}
|
|
1651
2149
|
/**
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1654
|
-
* @param {
|
|
1655
|
-
* @param {
|
|
2150
|
+
* Converts an array of data into a tab-separated text format and triggers a download.
|
|
2151
|
+
*
|
|
2152
|
+
* @param {string[]} headers - An array of strings for the header row.
|
|
2153
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2154
|
+
* @param {string} fileName - The desired name for the downloaded text file.
|
|
1656
2155
|
*/
|
|
1657
2156
|
function exportToText(headers, data, fileName) {
|
|
1658
2157
|
const textRows = [];
|
|
@@ -1670,12 +2169,13 @@ function exportToText(headers, data, fileName) {
|
|
|
1670
2169
|
}
|
|
1671
2170
|
|
|
1672
2171
|
/**
|
|
1673
|
-
* Opens a new window with
|
|
1674
|
-
*
|
|
1675
|
-
* @param {
|
|
1676
|
-
* @param {
|
|
1677
|
-
* @param {
|
|
1678
|
-
* @param {boolean} [opt.
|
|
2172
|
+
* Opens a new browser window or tab with a specified URL, providing options for the target and security attributes.
|
|
2173
|
+
*
|
|
2174
|
+
* @param {string} url - The URL to open in the new window.
|
|
2175
|
+
* @param {object} [opt] - Optional configuration for the new window.
|
|
2176
|
+
* @param {string} [opt.target='_blank'] - The target attribute for the link, specifying where to open the content (e.g., '_blank', '_self').
|
|
2177
|
+
* @param {boolean} [opt.noopener=true] - If `true`, adds `noopener` to the window features to prevent the new window from accessing the original window's object.
|
|
2178
|
+
* @param {boolean} [opt.noreferrer=true] - If `true`, adds `noreferrer` to prevent the browser from sending the `Referer` HTTP header.
|
|
1679
2179
|
*/
|
|
1680
2180
|
function openWindow(url, opt) {
|
|
1681
2181
|
const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
|
|
@@ -1689,9 +2189,11 @@ function openWindow(url, opt) {
|
|
|
1689
2189
|
window.open(url, target, features.join(','));
|
|
1690
2190
|
}
|
|
1691
2191
|
/**
|
|
1692
|
-
*
|
|
1693
|
-
*
|
|
1694
|
-
*
|
|
2192
|
+
* Asynchronously copies a given string to the user's clipboard. It uses the modern `navigator.clipboard` API
|
|
2193
|
+
* with a fallback to the deprecated `document.execCommand` for older browsers.
|
|
2194
|
+
*
|
|
2195
|
+
* @param {string} text - The string to be copied to the clipboard.
|
|
2196
|
+
* @returns {Promise<void>} A promise that resolves when the text has been successfully copied.
|
|
1695
2197
|
*/
|
|
1696
2198
|
async function copyToClipboard(text) {
|
|
1697
2199
|
if (navigator.clipboard) {
|
|
@@ -1708,8 +2210,10 @@ async function copyToClipboard(text) {
|
|
|
1708
2210
|
}
|
|
1709
2211
|
}
|
|
1710
2212
|
/**
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
2213
|
+
* Smoothly scrolls the window to the top of the page using a `requestAnimationFrame` loop
|
|
2214
|
+
* for a fluid animation.
|
|
2215
|
+
*
|
|
2216
|
+
* @param {number} [duration=300] - The total duration of the scroll animation in milliseconds.
|
|
1713
2217
|
*/
|
|
1714
2218
|
function scrollToTop(duration = 300) {
|
|
1715
2219
|
const start = window.scrollY;
|
|
@@ -1725,9 +2229,10 @@ function scrollToTop(duration = 300) {
|
|
|
1725
2229
|
requestAnimationFrame(scroll);
|
|
1726
2230
|
}
|
|
1727
2231
|
/**
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
* @
|
|
2232
|
+
* Retrieves the value of a specified query parameter from the current URL's search string.
|
|
2233
|
+
*
|
|
2234
|
+
* @param {string} paramName - The name of the query parameter to retrieve.
|
|
2235
|
+
* @returns {string | null} The value of the query parameter, or `null` if the parameter is not present in the URL.
|
|
1731
2236
|
*/
|
|
1732
2237
|
function getQueryParam(paramName) {
|
|
1733
2238
|
const urlParams = new URLSearchParams(window.location.search);
|
|
@@ -1735,7 +2240,7 @@ function getQueryParam(paramName) {
|
|
|
1735
2240
|
}
|
|
1736
2241
|
|
|
1737
2242
|
/**
|
|
1738
|
-
* Disables the right-click context menu on the window.
|
|
2243
|
+
* Disables the default right-click context menu on the entire window.
|
|
1739
2244
|
*/
|
|
1740
2245
|
function disableRightClick() {
|
|
1741
2246
|
const handler = (event) => event.preventDefault();
|
|
@@ -1745,7 +2250,7 @@ function disableRightClick() {
|
|
|
1745
2250
|
}
|
|
1746
2251
|
disableRightClick.handler = null;
|
|
1747
2252
|
/**
|
|
1748
|
-
*
|
|
2253
|
+
* Re-enables the right-click context menu if it was previously disabled by `disableRightClick`.
|
|
1749
2254
|
*/
|
|
1750
2255
|
function enableRightClick() {
|
|
1751
2256
|
if (disableRightClick.handler) {
|
|
@@ -1753,8 +2258,9 @@ function enableRightClick() {
|
|
|
1753
2258
|
}
|
|
1754
2259
|
}
|
|
1755
2260
|
/**
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
2261
|
+
* Prevents the default action for specific mouse buttons on the `mousedown` event.
|
|
2262
|
+
*
|
|
2263
|
+
* @param {number[]} buttons - An array of mouse button codes to disable (0 for left, 1 for middle, 2 for right).
|
|
1758
2264
|
*/
|
|
1759
2265
|
function disableMouseButtons(buttons) {
|
|
1760
2266
|
const handler = (event) => {
|
|
@@ -1769,7 +2275,7 @@ function disableMouseButtons(buttons) {
|
|
|
1769
2275
|
}
|
|
1770
2276
|
disableMouseButtons.handlers = [];
|
|
1771
2277
|
/**
|
|
1772
|
-
*
|
|
2278
|
+
* Re-enables all mouse buttons that were previously disabled by `disableMouseButtons`.
|
|
1773
2279
|
*/
|
|
1774
2280
|
function enableMouseButtons() {
|
|
1775
2281
|
for (const handler of disableMouseButtons.handlers) {
|
|
@@ -1779,17 +2285,19 @@ function enableMouseButtons() {
|
|
|
1779
2285
|
disableMouseButtons.handlers = [];
|
|
1780
2286
|
}
|
|
1781
2287
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
* @param {
|
|
2288
|
+
* Attaches a `dblclick` event listener to a specified HTML element.
|
|
2289
|
+
*
|
|
2290
|
+
* @param {HTMLElement} element - The DOM element to attach the listener to.
|
|
2291
|
+
* @param {(event: MouseEvent) => void} callback - The function to execute when the element is double-clicked.
|
|
1785
2292
|
*/
|
|
1786
2293
|
function addDoubleClickListener(element, callback) {
|
|
1787
2294
|
element.addEventListener('dblclick', callback);
|
|
1788
2295
|
}
|
|
1789
2296
|
/**
|
|
1790
|
-
* Removes a
|
|
1791
|
-
*
|
|
1792
|
-
* @param {
|
|
2297
|
+
* Removes a `dblclick` event listener from a specified HTML element.
|
|
2298
|
+
*
|
|
2299
|
+
* @param {HTMLElement} element - The DOM element to remove the listener from.
|
|
2300
|
+
* @param {(event: MouseEvent) => void} callback - The callback function that was originally added.
|
|
1793
2301
|
*/
|
|
1794
2302
|
function removeDoubleClickListener(element, callback) {
|
|
1795
2303
|
element.removeEventListener('dblclick', callback);
|
|
@@ -1797,9 +2305,10 @@ function removeDoubleClickListener(element, callback) {
|
|
|
1797
2305
|
// Example usage:
|
|
1798
2306
|
// addDoubleClickListener(document.body, () => alert('Double clicked!'));
|
|
1799
2307
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
* @param {
|
|
2308
|
+
* Sets up a global click listener to detect when a user clicks outside of a specified element.
|
|
2309
|
+
*
|
|
2310
|
+
* @param {HTMLElement} element - The element to monitor for outside clicks.
|
|
2311
|
+
* @param {() => void} callback - The function to execute when a click outside the element is detected.
|
|
1803
2312
|
*/
|
|
1804
2313
|
function clickOutside(element, callback) {
|
|
1805
2314
|
const handler = (event) => {
|
|
@@ -1813,8 +2322,9 @@ function clickOutside(element, callback) {
|
|
|
1813
2322
|
}
|
|
1814
2323
|
clickOutside.handlers = [];
|
|
1815
2324
|
/**
|
|
1816
|
-
* Removes the click outside listener for a specific element
|
|
1817
|
-
*
|
|
2325
|
+
* Removes the "click outside" event listener for a specific element that was added by `clickOutside`.
|
|
2326
|
+
*
|
|
2327
|
+
* @param {HTMLElement} element - The element for which to remove the listener.
|
|
1818
2328
|
*/
|
|
1819
2329
|
function removeClickOutside(element) {
|
|
1820
2330
|
const index = clickOutside.handlers.findIndex(h => h.element === element);
|
|
@@ -1829,7 +2339,8 @@ function removeClickOutside(element) {
|
|
|
1829
2339
|
// clickOutside(menu, () => menu.style.display = 'none');
|
|
1830
2340
|
// }
|
|
1831
2341
|
/**
|
|
1832
|
-
* Disables the F12 key and
|
|
2342
|
+
* Disables the F12 key and common developer tool shortcuts (Ctrl+Shift+I, Ctrl+Shift+J)
|
|
2343
|
+
* to prevent users from easily opening the browser's developer console.
|
|
1833
2344
|
*/
|
|
1834
2345
|
function disableF12Key() {
|
|
1835
2346
|
const handler = function (event) {
|
|
@@ -1838,8 +2349,9 @@ function disableF12Key() {
|
|
|
1838
2349
|
document.addEventListener('keydown', handler);
|
|
1839
2350
|
}
|
|
1840
2351
|
/**
|
|
1841
|
-
* Enables or disables the
|
|
1842
|
-
*
|
|
2352
|
+
* Enables or disables the ability to navigate through focusable elements using the Tab key.
|
|
2353
|
+
*
|
|
2354
|
+
* @param {boolean} enable - If `true`, tab navigation is enabled; if `false`, it is disabled.
|
|
1843
2355
|
*/
|
|
1844
2356
|
function toggleTabNavigation(enable) {
|
|
1845
2357
|
if (enable) {
|
|
@@ -1856,7 +2368,7 @@ function toggleTabNavigation(enable) {
|
|
|
1856
2368
|
}
|
|
1857
2369
|
}
|
|
1858
2370
|
/**
|
|
1859
|
-
*
|
|
2371
|
+
* Prevents users from copying content from the page by intercepting the `copy` event.
|
|
1860
2372
|
*/
|
|
1861
2373
|
function disableCopy() {
|
|
1862
2374
|
document.addEventListener('copy', (event) => {
|
|
@@ -1865,11 +2377,12 @@ function disableCopy() {
|
|
|
1865
2377
|
});
|
|
1866
2378
|
}
|
|
1867
2379
|
/**
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1870
|
-
* @param {
|
|
1871
|
-
* @param {
|
|
1872
|
-
* @param {boolean} [
|
|
2380
|
+
* Registers a global keyboard shortcut that triggers a callback when a specific key combination is pressed.
|
|
2381
|
+
*
|
|
2382
|
+
* @param {string} key - The main key for the shortcut (e.g., 'S', 'F1').
|
|
2383
|
+
* @param {() => void} callback - The function to execute when the shortcut is pressed.
|
|
2384
|
+
* @param {boolean} [ctrlKey=false] - If `true`, the Ctrl key must be pressed.
|
|
2385
|
+
* @param {boolean} [shiftKey=false] - If `true`, the Shift key must be pressed.
|
|
1873
2386
|
*/
|
|
1874
2387
|
function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = false) {
|
|
1875
2388
|
document.addEventListener('keydown', (event) => {
|
|
@@ -1882,10 +2395,11 @@ function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = fa
|
|
|
1882
2395
|
});
|
|
1883
2396
|
}
|
|
1884
2397
|
/**
|
|
1885
|
-
* Removes a
|
|
1886
|
-
*
|
|
1887
|
-
* @param {
|
|
1888
|
-
* @param {boolean} [
|
|
2398
|
+
* Removes a global keyboard shortcut that was previously added.
|
|
2399
|
+
*
|
|
2400
|
+
* @param {string} key - The main key of the shortcut to remove.
|
|
2401
|
+
* @param {boolean} [ctrlKey=false] - The Ctrl key modifier of the shortcut.
|
|
2402
|
+
* @param {boolean} [shiftKey=false] - The Shift key modifier of the shortcut.
|
|
1889
2403
|
*/
|
|
1890
2404
|
function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
|
|
1891
2405
|
const handler = (event) => {
|
|
@@ -1898,8 +2412,9 @@ function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
|
|
|
1898
2412
|
document.removeEventListener('keydown', handler);
|
|
1899
2413
|
}
|
|
1900
2414
|
/**
|
|
1901
|
-
* Disables
|
|
1902
|
-
*
|
|
2415
|
+
* Disables a list of specified keys or key combinations.
|
|
2416
|
+
*
|
|
2417
|
+
* @param {string[]} keys - An array of key names or combinations (e.g., 'F1', 'Control+S') to disable.
|
|
1903
2418
|
*/
|
|
1904
2419
|
function disableSpecificKeys(keys) {
|
|
1905
2420
|
const handler = (event) => {
|
|
@@ -1914,7 +2429,7 @@ function disableSpecificKeys(keys) {
|
|
|
1914
2429
|
}
|
|
1915
2430
|
disableSpecificKeys.handlers = [];
|
|
1916
2431
|
/**
|
|
1917
|
-
*
|
|
2432
|
+
* Re-enables all keys that were previously disabled by `disableSpecificKeys`.
|
|
1918
2433
|
*/
|
|
1919
2434
|
function enableSpecificKeys() {
|
|
1920
2435
|
for (const handler of disableSpecificKeys.handlers) {
|
|
@@ -1923,8 +2438,9 @@ function enableSpecificKeys() {
|
|
|
1923
2438
|
disableSpecificKeys.handlers = [];
|
|
1924
2439
|
}
|
|
1925
2440
|
/**
|
|
1926
|
-
* Registers multiple keyboard shortcuts
|
|
1927
|
-
*
|
|
2441
|
+
* Registers multiple keyboard shortcuts from an array of shortcut configurations.
|
|
2442
|
+
*
|
|
2443
|
+
* @param {Array<{ key: string; ctrlKey?: boolean; shiftKey?: boolean; altKey?: boolean; callback: () => void }>} shortcuts - An array of shortcut objects.
|
|
1928
2444
|
*/
|
|
1929
2445
|
function registerKeyboardShortcuts(shortcuts) {
|
|
1930
2446
|
const handler = (event) => {
|
|
@@ -1944,7 +2460,7 @@ function registerKeyboardShortcuts(shortcuts) {
|
|
|
1944
2460
|
}
|
|
1945
2461
|
registerKeyboardShortcuts.handlers = [];
|
|
1946
2462
|
/**
|
|
1947
|
-
*
|
|
2463
|
+
* Removes all keyboard shortcuts that were registered using `registerKeyboardShortcuts`.
|
|
1948
2464
|
*/
|
|
1949
2465
|
function unregisterKeyboardShortcuts() {
|
|
1950
2466
|
for (const handler of registerKeyboardShortcuts.handlers) {
|
|
@@ -1953,9 +2469,10 @@ function unregisterKeyboardShortcuts() {
|
|
|
1953
2469
|
registerKeyboardShortcuts.handlers = [];
|
|
1954
2470
|
}
|
|
1955
2471
|
/**
|
|
1956
|
-
* Adds a listener for a specific key
|
|
1957
|
-
*
|
|
1958
|
-
* @param {
|
|
2472
|
+
* Adds a global `keydown` listener for a specific key.
|
|
2473
|
+
*
|
|
2474
|
+
* @param {string} key - The key to listen for (e.g., 'Enter', 'Escape').
|
|
2475
|
+
* @param {() => void} callback - The function to execute when the key is pressed.
|
|
1959
2476
|
*/
|
|
1960
2477
|
function addKeyListener(key, callback) {
|
|
1961
2478
|
const handler = (event) => {
|
|
@@ -1970,7 +2487,7 @@ function addKeyListener(key, callback) {
|
|
|
1970
2487
|
}
|
|
1971
2488
|
addKeyListener.handlers = [];
|
|
1972
2489
|
/**
|
|
1973
|
-
* Removes all
|
|
2490
|
+
* Removes all key listeners that were added using `addKeyListener`.
|
|
1974
2491
|
*/
|
|
1975
2492
|
function removeKeyListeners() {
|
|
1976
2493
|
for (const handler of addKeyListener.handlers) {
|
|
@@ -1979,9 +2496,10 @@ function removeKeyListeners() {
|
|
|
1979
2496
|
addKeyListener.handlers = [];
|
|
1980
2497
|
}
|
|
1981
2498
|
/**
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
* @param {
|
|
2499
|
+
* Executes a callback function repeatedly while a specific key is held down.
|
|
2500
|
+
*
|
|
2501
|
+
* @param {string} key - The key to monitor.
|
|
2502
|
+
* @param {() => void} onHold - The callback function to execute on each `keydown` event for the specified key.
|
|
1985
2503
|
*/
|
|
1986
2504
|
function detectKeyHold(key, onHold) {
|
|
1987
2505
|
const keyDownHandler = (event) => {
|
|
@@ -1995,7 +2513,7 @@ function detectKeyHold(key, onHold) {
|
|
|
1995
2513
|
}
|
|
1996
2514
|
detectKeyHold.handlers = [];
|
|
1997
2515
|
/**
|
|
1998
|
-
*
|
|
2516
|
+
* Removes all key hold listeners that were added by `detectKeyHold`.
|
|
1999
2517
|
*/
|
|
2000
2518
|
function stopDetectingKeyHold() {
|
|
2001
2519
|
for (const handler of detectKeyHold.handlers) {
|
|
@@ -2004,8 +2522,9 @@ function stopDetectingKeyHold() {
|
|
|
2004
2522
|
detectKeyHold.handlers = [];
|
|
2005
2523
|
}
|
|
2006
2524
|
/**
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2525
|
+
* Creates and maintains a `Set` of currently pressed keys.
|
|
2526
|
+
*
|
|
2527
|
+
* @returns {Set<string>} A `Set` that dynamically updates with the keys being pressed.
|
|
2009
2528
|
*/
|
|
2010
2529
|
function createKeyMap() {
|
|
2011
2530
|
const pressedKeys = new Set();
|
|
@@ -2025,13 +2544,14 @@ function createKeyMap() {
|
|
|
2025
2544
|
return pressedKeys;
|
|
2026
2545
|
}
|
|
2027
2546
|
/**
|
|
2028
|
-
* Clears
|
|
2547
|
+
* Clears the `keydown` and `keyup` event listeners created by `createKeyMap`.
|
|
2029
2548
|
*/
|
|
2030
2549
|
createKeyMap.clearListeners = () => { };
|
|
2031
2550
|
/**
|
|
2032
|
-
* Sets up
|
|
2033
|
-
*
|
|
2034
|
-
* @param {
|
|
2551
|
+
* Sets up a keyboard shortcut that triggers a callback when a specific combination of keys is held down, regardless of order.
|
|
2552
|
+
*
|
|
2553
|
+
* @param {string[]} keys - An array of keys that constitute the shortcut.
|
|
2554
|
+
* @param {() => void} callback - The function to execute when the key combination is active.
|
|
2035
2555
|
*/
|
|
2036
2556
|
function customShortcut(keys, callback) {
|
|
2037
2557
|
const pressedKeys = new Set();
|
|
@@ -2051,7 +2571,7 @@ function customShortcut(keys, callback) {
|
|
|
2051
2571
|
}
|
|
2052
2572
|
customShortcut.handlers = [];
|
|
2053
2573
|
/**
|
|
2054
|
-
* Removes all
|
|
2574
|
+
* Removes all keyboard shortcut listeners that were added by `customShortcut`.
|
|
2055
2575
|
*/
|
|
2056
2576
|
function removeCustomShortcuts() {
|
|
2057
2577
|
for (const { keyDownHandler, keyUpHandler } of customShortcut.handlers) {
|
|
@@ -2061,11 +2581,12 @@ function removeCustomShortcuts() {
|
|
|
2061
2581
|
customShortcut.handlers = [];
|
|
2062
2582
|
}
|
|
2063
2583
|
/**
|
|
2064
|
-
*
|
|
2065
|
-
*
|
|
2066
|
-
* @param {
|
|
2067
|
-
* @param {boolean}
|
|
2068
|
-
* @param {boolean}
|
|
2584
|
+
* Programmatically dispatches a `keydown` event to simulate a key press.
|
|
2585
|
+
*
|
|
2586
|
+
* @param {string} key - The key to simulate (e.g., 'Enter', 'a').
|
|
2587
|
+
* @param {boolean} [ctrlKey=false] - Whether to simulate the Ctrl key being pressed.
|
|
2588
|
+
* @param {boolean} [shiftKey=false] - Whether to simulate the Shift key being pressed.
|
|
2589
|
+
* @param {boolean} [altKey=false] - Whether to simulate the Alt key being pressed.
|
|
2069
2590
|
*/
|
|
2070
2591
|
function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false) {
|
|
2071
2592
|
const event = new KeyboardEvent('keydown', {
|
|
@@ -2080,9 +2601,11 @@ function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false
|
|
|
2080
2601
|
}
|
|
2081
2602
|
|
|
2082
2603
|
/**
|
|
2083
|
-
* Parses a date string
|
|
2084
|
-
*
|
|
2085
|
-
*
|
|
2604
|
+
* Parses a date string in the format 'YYYY-MM-DD' and returns a `Date` object.
|
|
2605
|
+
* It includes validation to ensure the parsed date is a valid calendar date.
|
|
2606
|
+
*
|
|
2607
|
+
* @param {string} dateString - The date string to parse.
|
|
2608
|
+
* @returns {Date | null} A `Date` object if the string is a valid date, otherwise `null`.
|
|
2086
2609
|
*/
|
|
2087
2610
|
function parseDate(dateString) {
|
|
2088
2611
|
const parts = dateString.split('-');
|
|
@@ -2098,9 +2621,11 @@ function parseDate(dateString) {
|
|
|
2098
2621
|
return null;
|
|
2099
2622
|
}
|
|
2100
2623
|
/**
|
|
2101
|
-
* Formats a Date object into a string.
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2624
|
+
* Formats a `Date` object into a custom string format.
|
|
2625
|
+
* Supported format specifiers: YYYY, MM, DD, HH, mm, ss.
|
|
2626
|
+
*
|
|
2627
|
+
* @param {Date} date - The `Date` object to format.
|
|
2628
|
+
* @param {string} format - The desired string format (e.g., 'YYYY-MM-DD HH:mm:ss').
|
|
2104
2629
|
* @returns {string} The formatted date string.
|
|
2105
2630
|
*/
|
|
2106
2631
|
function formatDate(date, format) {
|
|
@@ -2115,9 +2640,10 @@ function formatDate(date, format) {
|
|
|
2115
2640
|
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (matched) => (map[matched] || matched).toString());
|
|
2116
2641
|
}
|
|
2117
2642
|
/**
|
|
2118
|
-
* Calculates the number of days between two dates.
|
|
2119
|
-
*
|
|
2120
|
-
* @param {Date}
|
|
2643
|
+
* Calculates the total number of full days between two dates.
|
|
2644
|
+
*
|
|
2645
|
+
* @param {Date} startDate - The starting date.
|
|
2646
|
+
* @param {Date} endDate - The ending date.
|
|
2121
2647
|
* @returns {number} The number of days between the two dates.
|
|
2122
2648
|
*/
|
|
2123
2649
|
function daysBetween(startDate, endDate) {
|
|
@@ -2125,10 +2651,11 @@ function daysBetween(startDate, endDate) {
|
|
|
2125
2651
|
return Math.round((endDate.getTime() - startDate.getTime()) / millisecondsPerDay);
|
|
2126
2652
|
}
|
|
2127
2653
|
/**
|
|
2128
|
-
* Adds a specified number of days to a date.
|
|
2129
|
-
*
|
|
2130
|
-
* @param {
|
|
2131
|
-
* @
|
|
2654
|
+
* Adds a specified number of days to a given date.
|
|
2655
|
+
*
|
|
2656
|
+
* @param {Date} date - The original date.
|
|
2657
|
+
* @param {number} days - The number of days to add (can be negative to subtract).
|
|
2658
|
+
* @returns {Date} A new `Date` object representing the resulting date.
|
|
2132
2659
|
*/
|
|
2133
2660
|
function addDays(date, days) {
|
|
2134
2661
|
const result = new Date(date);
|
|
@@ -2136,41 +2663,46 @@ function addDays(date, days) {
|
|
|
2136
2663
|
return result;
|
|
2137
2664
|
}
|
|
2138
2665
|
/**
|
|
2139
|
-
* Subtracts a specified number of days from a date.
|
|
2140
|
-
*
|
|
2141
|
-
* @param {
|
|
2142
|
-
* @
|
|
2666
|
+
* Subtracts a specified number of days from a given date.
|
|
2667
|
+
*
|
|
2668
|
+
* @param {Date} date - The original date.
|
|
2669
|
+
* @param {number} days - The number of days to subtract.
|
|
2670
|
+
* @returns {Date} A new `Date` object representing the resulting date.
|
|
2143
2671
|
*/
|
|
2144
2672
|
function subtractDays(date, days) {
|
|
2145
2673
|
return addDays(date, -days);
|
|
2146
2674
|
}
|
|
2147
2675
|
/**
|
|
2148
|
-
* Determines
|
|
2149
|
-
*
|
|
2150
|
-
* @
|
|
2676
|
+
* Determines whether a given year is a leap year according to the Gregorian calendar rules.
|
|
2677
|
+
*
|
|
2678
|
+
* @param {number} year - The year to check.
|
|
2679
|
+
* @returns {boolean} `true` if the year is a leap year, otherwise `false`.
|
|
2151
2680
|
*/
|
|
2152
2681
|
function isLeapYear(year) {
|
|
2153
2682
|
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
2154
2683
|
}
|
|
2155
2684
|
/**
|
|
2156
|
-
*
|
|
2157
|
-
*
|
|
2158
|
-
* @
|
|
2685
|
+
* Returns a new `Date` object set to the first day of the month for a given date.
|
|
2686
|
+
*
|
|
2687
|
+
* @param {Date} date - The date from which to determine the month and year.
|
|
2688
|
+
* @returns {Date} A `Date` object representing the start of the month.
|
|
2159
2689
|
*/
|
|
2160
2690
|
function getStartOfMonth(date) {
|
|
2161
2691
|
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
2162
2692
|
}
|
|
2163
2693
|
/**
|
|
2164
|
-
*
|
|
2165
|
-
*
|
|
2166
|
-
* @
|
|
2694
|
+
* Returns a new `Date` object set to the last day of the month for a given date.
|
|
2695
|
+
*
|
|
2696
|
+
* @param {Date} date - The date from which to determine the month and year.
|
|
2697
|
+
* @returns {Date} A `Date` object representing the end of the month.
|
|
2167
2698
|
*/
|
|
2168
2699
|
function getEndOfMonth(date) {
|
|
2169
2700
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
2170
2701
|
}
|
|
2171
2702
|
/**
|
|
2172
|
-
* Calculates age
|
|
2173
|
-
*
|
|
2703
|
+
* Calculates the current age in years based on a given birth date.
|
|
2704
|
+
*
|
|
2705
|
+
* @param {Date} birthDate - The date of birth.
|
|
2174
2706
|
* @returns {number} The calculated age.
|
|
2175
2707
|
*/
|
|
2176
2708
|
function calculateAge(birthDate) {
|
|
@@ -2183,8 +2715,9 @@ function calculateAge(birthDate) {
|
|
|
2183
2715
|
return age;
|
|
2184
2716
|
}
|
|
2185
2717
|
/**
|
|
2186
|
-
* Calculates the number of days until the next birthday.
|
|
2187
|
-
*
|
|
2718
|
+
* Calculates the number of days from the current date until the next birthday.
|
|
2719
|
+
*
|
|
2720
|
+
* @param {Date} birthDate - The date of birth.
|
|
2188
2721
|
* @returns {number} The number of days until the next birthday.
|
|
2189
2722
|
*/
|
|
2190
2723
|
function daysToNextBirthday(birthDate) {
|
|
@@ -2198,10 +2731,11 @@ function daysToNextBirthday(birthDate) {
|
|
|
2198
2731
|
return daysBetween(today, nextBirthday);
|
|
2199
2732
|
}
|
|
2200
2733
|
/**
|
|
2201
|
-
* Calculates the age
|
|
2202
|
-
*
|
|
2203
|
-
* @param {Date}
|
|
2204
|
-
* @
|
|
2734
|
+
* Calculates the age of a person on a specific date in the past or future.
|
|
2735
|
+
*
|
|
2736
|
+
* @param {Date} birthDate - The date of birth.
|
|
2737
|
+
* @param {Date} atDate - The target date for which to calculate the age.
|
|
2738
|
+
* @returns {number} The age on the specified date.
|
|
2205
2739
|
*/
|
|
2206
2740
|
function ageAtDate(birthDate, atDate) {
|
|
2207
2741
|
let age = atDate.getFullYear() - birthDate.getFullYear();
|
|
@@ -2213,11 +2747,12 @@ function ageAtDate(birthDate, atDate) {
|
|
|
2213
2747
|
}
|
|
2214
2748
|
|
|
2215
2749
|
/**
|
|
2216
|
-
* Creates a debounced asynchronous validator function.
|
|
2750
|
+
* Creates a debounced version of an asynchronous validator function. This is useful for scenarios
|
|
2751
|
+
* like form input validation where you want to delay validation until the user has stopped typing.
|
|
2217
2752
|
*
|
|
2218
|
-
* @param validator - The
|
|
2219
|
-
* @param delay - The debounce delay in milliseconds.
|
|
2220
|
-
* @returns A
|
|
2753
|
+
* @param validator - The asynchronous validator function to be debounced. It receives the value to validate and a `debounce` function.
|
|
2754
|
+
* @param {number} delay - The debounce delay in milliseconds.
|
|
2755
|
+
* @returns A new function that takes a value and returns a promise that resolves or rejects based on the debounced validation.
|
|
2221
2756
|
*/
|
|
2222
2757
|
function debounceAsyncValidator(validator, delay) {
|
|
2223
2758
|
let currentPromiseReject = null;
|
|
@@ -2248,10 +2783,13 @@ function debounceAsyncValidator(validator, delay) {
|
|
|
2248
2783
|
};
|
|
2249
2784
|
}
|
|
2250
2785
|
/**
|
|
2251
|
-
* Creates a debounced version of an asynchronous function.
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
2254
|
-
* @
|
|
2786
|
+
* Creates a debounced version of an asynchronous function. The debounced function will only
|
|
2787
|
+
* resolve the promise of the last invocation within the `wait` period.
|
|
2788
|
+
*
|
|
2789
|
+
* @template T - The type of the asynchronous function.
|
|
2790
|
+
* @param {T} func - The asynchronous function to debounce.
|
|
2791
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2792
|
+
* @returns A new debounced asynchronous function.
|
|
2255
2793
|
*/
|
|
2256
2794
|
function debounceAsync(func, wait) {
|
|
2257
2795
|
let timeoutReject = null;
|
|
@@ -2270,10 +2808,13 @@ function debounceAsync(func, wait) {
|
|
|
2270
2808
|
};
|
|
2271
2809
|
}
|
|
2272
2810
|
/**
|
|
2273
|
-
* Creates a debounced asynchronous function that executes immediately on the first call
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
* @
|
|
2811
|
+
* Creates a debounced version of an asynchronous function that executes immediately on the first call
|
|
2812
|
+
* and then waits for the specified delay before allowing the next execution.
|
|
2813
|
+
*
|
|
2814
|
+
* @template T - The type of the asynchronous function.
|
|
2815
|
+
* @param {T} func - The asynchronous function to debounce.
|
|
2816
|
+
* @param {number} wait - The cooldown period in milliseconds after an immediate execution.
|
|
2817
|
+
* @returns A new debounced asynchronous function that executes on the leading edge.
|
|
2277
2818
|
*/
|
|
2278
2819
|
function debounceAsyncWithImmediate(func, wait) {
|
|
2279
2820
|
let timeoutReject = null;
|
|
@@ -2305,10 +2846,13 @@ function debounceAsyncWithImmediate(func, wait) {
|
|
|
2305
2846
|
};
|
|
2306
2847
|
}
|
|
2307
2848
|
/**
|
|
2308
|
-
* Creates a debounced
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2311
|
-
* @
|
|
2849
|
+
* Creates a debounced function that invokes `func` on the leading edge of the `wait` timeout.
|
|
2850
|
+
* Subsequent calls within the `wait` period are ignored.
|
|
2851
|
+
*
|
|
2852
|
+
* @template T - The type of the function.
|
|
2853
|
+
* @param {T} func - The function to debounce.
|
|
2854
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2855
|
+
* @returns A new debounced function.
|
|
2312
2856
|
*/
|
|
2313
2857
|
function debounceLeading(func, wait) {
|
|
2314
2858
|
let timeoutReject = null;
|
|
@@ -2329,10 +2873,13 @@ function debounceLeading(func, wait) {
|
|
|
2329
2873
|
};
|
|
2330
2874
|
}
|
|
2331
2875
|
/**
|
|
2332
|
-
* Creates a debounced
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
* @
|
|
2876
|
+
* Creates a debounced function that invokes `func` on the trailing edge of the `wait` timeout.
|
|
2877
|
+
* The function is called only after `wait` milliseconds of inactivity.
|
|
2878
|
+
*
|
|
2879
|
+
* @template T - The type of the function.
|
|
2880
|
+
* @param {T} func - The function to debounce.
|
|
2881
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2882
|
+
* @returns A new debounced function.
|
|
2336
2883
|
*/
|
|
2337
2884
|
function debounceTrailing(func, wait) {
|
|
2338
2885
|
let timeoutReject = null;
|
|
@@ -2350,10 +2897,13 @@ function debounceTrailing(func, wait) {
|
|
|
2350
2897
|
};
|
|
2351
2898
|
}
|
|
2352
2899
|
/**
|
|
2353
|
-
* Creates a debounced
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
* @
|
|
2900
|
+
* Creates a debounced function that invokes `func` on both the leading and trailing edges of the `wait` timeout.
|
|
2901
|
+
* This is useful for UIs where an action should happen immediately on the first event, but also after a pause in events.
|
|
2902
|
+
*
|
|
2903
|
+
* @template T - The type of the function.
|
|
2904
|
+
* @param {T} func - The function to debounce.
|
|
2905
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2906
|
+
* @returns A new debounced function.
|
|
2357
2907
|
*/
|
|
2358
2908
|
function debounceLeadingTrailing(func, wait) {
|
|
2359
2909
|
let timeoutReject = null;
|
|
@@ -2379,10 +2929,13 @@ function debounceLeadingTrailing(func, wait) {
|
|
|
2379
2929
|
};
|
|
2380
2930
|
}
|
|
2381
2931
|
/**
|
|
2382
|
-
* Creates a debounced
|
|
2383
|
-
*
|
|
2384
|
-
*
|
|
2385
|
-
* @
|
|
2932
|
+
* Creates a standard debounced function that delays invoking `func` until after `wait` milliseconds
|
|
2933
|
+
* have elapsed since the last time the debounced function was invoked. (This is an alias for `debounceTrailing`).
|
|
2934
|
+
*
|
|
2935
|
+
* @template T - The type of the function.
|
|
2936
|
+
* @param {T} func - The function to debounce.
|
|
2937
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2938
|
+
* @returns A new debounced function.
|
|
2386
2939
|
*/
|
|
2387
2940
|
function debounce(func, wait) {
|
|
2388
2941
|
let timeoutReject = null;
|
|
@@ -2399,10 +2952,13 @@ function debounce(func, wait) {
|
|
|
2399
2952
|
};
|
|
2400
2953
|
}
|
|
2401
2954
|
/**
|
|
2402
|
-
* Creates a throttled
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
* @
|
|
2955
|
+
* Creates a throttled function that only invokes `func` at most once per every `limit` milliseconds.
|
|
2956
|
+
* This is useful for rate-limiting events that fire frequently, such as scrolling or resizing.
|
|
2957
|
+
*
|
|
2958
|
+
* @template T - The type of the function.
|
|
2959
|
+
* @param {T} func - The function to throttle.
|
|
2960
|
+
* @param {number} limit - The throttle duration in milliseconds.
|
|
2961
|
+
* @returns A new throttled function.
|
|
2406
2962
|
*/
|
|
2407
2963
|
function throttle(func, limit) {
|
|
2408
2964
|
let lastCall = 0;
|
|
@@ -2416,9 +2972,11 @@ function throttle(func, limit) {
|
|
|
2416
2972
|
}
|
|
2417
2973
|
|
|
2418
2974
|
/**
|
|
2419
|
-
* Converts a FormData object
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2975
|
+
* Converts a `FormData` object into a regular JavaScript object. It correctly handles
|
|
2976
|
+
* multiple values for the same key by creating an array for that key.
|
|
2977
|
+
*
|
|
2978
|
+
* @param {FormData} formData - The `FormData` object to convert.
|
|
2979
|
+
* @returns {Record<string, any>} A plain JavaScript object representation of the FormData.
|
|
2422
2980
|
*/
|
|
2423
2981
|
function formDataToObject(formData) {
|
|
2424
2982
|
const obj = {};
|
|
@@ -2437,9 +2995,10 @@ function formDataToObject(formData) {
|
|
|
2437
2995
|
return obj;
|
|
2438
2996
|
}
|
|
2439
2997
|
/**
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
2442
|
-
* @
|
|
2998
|
+
* Asynchronously reads the content of a `File` object as a text string.
|
|
2999
|
+
*
|
|
3000
|
+
* @param {File} file - The `File` object to read.
|
|
3001
|
+
* @returns {Promise<string>} A promise that resolves with the text content of the file.
|
|
2443
3002
|
*/
|
|
2444
3003
|
function readFileAsText(file) {
|
|
2445
3004
|
return new Promise((resolve, reject) => {
|
|
@@ -2450,9 +3009,10 @@ function readFileAsText(file) {
|
|
|
2450
3009
|
});
|
|
2451
3010
|
}
|
|
2452
3011
|
/**
|
|
2453
|
-
*
|
|
2454
|
-
*
|
|
2455
|
-
* @
|
|
3012
|
+
* Asynchronously reads the content of a `File` object as a Base64-encoded Data URL.
|
|
3013
|
+
*
|
|
3014
|
+
* @param {File} file - The `File` object to read.
|
|
3015
|
+
* @returns {Promise<string>} A promise that resolves with the Data URL representing the file's content.
|
|
2456
3016
|
*/
|
|
2457
3017
|
function readFileAsDataURL(file) {
|
|
2458
3018
|
return new Promise((resolve, reject) => {
|
|
@@ -2463,27 +3023,30 @@ function readFileAsDataURL(file) {
|
|
|
2463
3023
|
});
|
|
2464
3024
|
}
|
|
2465
3025
|
/**
|
|
2466
|
-
* Creates a Blob from a string.
|
|
2467
|
-
*
|
|
2468
|
-
* @param {string}
|
|
2469
|
-
* @
|
|
3026
|
+
* Creates a `Blob` object from a string.
|
|
3027
|
+
*
|
|
3028
|
+
* @param {string} content - The string content to be put into the Blob.
|
|
3029
|
+
* @param {string} [type='text/plain'] - The MIME type of the Blob.
|
|
3030
|
+
* @returns {Blob} A new `Blob` object.
|
|
2470
3031
|
*/
|
|
2471
3032
|
function stringToBlob(content, type = 'text/plain') {
|
|
2472
3033
|
return new Blob([content], { type });
|
|
2473
3034
|
}
|
|
2474
3035
|
/**
|
|
2475
|
-
* Creates a Blob from an ArrayBuffer
|
|
2476
|
-
*
|
|
2477
|
-
* @param {
|
|
2478
|
-
* @
|
|
3036
|
+
* Creates a `Blob` object from an `ArrayBuffer`.
|
|
3037
|
+
*
|
|
3038
|
+
* @param {ArrayBuffer} buffer - The `ArrayBuffer` to be put into the Blob.
|
|
3039
|
+
* @param {string} [type='application/octet-stream'] - The MIME type of the Blob.
|
|
3040
|
+
* @returns {Blob} A new `Blob` object.
|
|
2479
3041
|
*/
|
|
2480
3042
|
function bufferToBlob(buffer, type = 'application/octet-stream') {
|
|
2481
3043
|
return new Blob([buffer], { type });
|
|
2482
3044
|
}
|
|
2483
3045
|
/**
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2486
|
-
* @param {
|
|
3046
|
+
* Triggers a browser download for a file created from a `Blob` object.
|
|
3047
|
+
*
|
|
3048
|
+
* @param {Blob} blob - The `Blob` containing the file data.
|
|
3049
|
+
* @param {string} fileName - The desired name for the downloaded file.
|
|
2487
3050
|
*/
|
|
2488
3051
|
function downloadBlob(blob, fileName) {
|
|
2489
3052
|
const link = document.createElement('a');
|
|
@@ -2498,11 +3061,12 @@ function downloadBlob(blob, fileName) {
|
|
|
2498
3061
|
URL.revokeObjectURL(url);
|
|
2499
3062
|
}
|
|
2500
3063
|
/**
|
|
2501
|
-
*
|
|
2502
|
-
*
|
|
2503
|
-
* @param {
|
|
2504
|
-
* @param {string}
|
|
2505
|
-
* @
|
|
3064
|
+
* Appends a `Blob` to a new `FormData` object.
|
|
3065
|
+
*
|
|
3066
|
+
* @param {Blob} blob - The `Blob` to append.
|
|
3067
|
+
* @param {string} name - The name of the field to append the blob as.
|
|
3068
|
+
* @param {string} [fileName='file'] - The filename to associate with the blob in the `FormData`.
|
|
3069
|
+
* @returns {FormData} A new `FormData` object containing the blob.
|
|
2506
3070
|
*/
|
|
2507
3071
|
function blobToFormData(blob, name, fileName = 'file') {
|
|
2508
3072
|
const formData = new FormData();
|
|
@@ -2511,9 +3075,10 @@ function blobToFormData(blob, name, fileName = 'file') {
|
|
|
2511
3075
|
}
|
|
2512
3076
|
|
|
2513
3077
|
/**
|
|
2514
|
-
* Capitalizes the first
|
|
2515
|
-
*
|
|
2516
|
-
* @
|
|
3078
|
+
* Capitalizes the first letter of a string.
|
|
3079
|
+
*
|
|
3080
|
+
* @param {string} s - The input string.
|
|
3081
|
+
* @returns {string} The string with the first letter capitalized.
|
|
2517
3082
|
*/
|
|
2518
3083
|
function upperFirst(s) {
|
|
2519
3084
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -2521,9 +3086,10 @@ function upperFirst(s) {
|
|
|
2521
3086
|
// Example usage:
|
|
2522
3087
|
// console.log(upperFirst('hello')); // 'Hello'
|
|
2523
3088
|
/**
|
|
2524
|
-
*
|
|
2525
|
-
*
|
|
2526
|
-
* @
|
|
3089
|
+
* Converts the first letter of a string to lowercase.
|
|
3090
|
+
*
|
|
3091
|
+
* @param {string} s - The input string.
|
|
3092
|
+
* @returns {string} The string with the first letter in lowercase.
|
|
2527
3093
|
*/
|
|
2528
3094
|
function lowerFirst(s) {
|
|
2529
3095
|
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
@@ -2531,9 +3097,11 @@ function lowerFirst(s) {
|
|
|
2531
3097
|
// Example usage:
|
|
2532
3098
|
// console.log(lowerFirst('Hello')); // 'hello'
|
|
2533
3099
|
/**
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2536
|
-
*
|
|
3100
|
+
* Normalizes a string by converting it to lowercase, trimming whitespace, replacing spaces with hyphens,
|
|
3101
|
+
* and removing diacritical marks (accents).
|
|
3102
|
+
*
|
|
3103
|
+
* @param {string} input - The string to normalize.
|
|
3104
|
+
* @returns {string} The URL-friendly, normalized string.
|
|
2537
3105
|
*/
|
|
2538
3106
|
function removeAccent(input) {
|
|
2539
3107
|
return input
|
|
@@ -2547,8 +3115,9 @@ function removeAccent(input) {
|
|
|
2547
3115
|
// Example usage:
|
|
2548
3116
|
// console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
|
|
2549
3117
|
/**
|
|
2550
|
-
* Reverses a string.
|
|
2551
|
-
*
|
|
3118
|
+
* Reverses the characters of a string.
|
|
3119
|
+
*
|
|
3120
|
+
* @param {string} str - The input string.
|
|
2552
3121
|
* @returns {string} The reversed string.
|
|
2553
3122
|
*/
|
|
2554
3123
|
function reverseString(str) {
|
|
@@ -2557,9 +3126,10 @@ function reverseString(str) {
|
|
|
2557
3126
|
// Example usage:
|
|
2558
3127
|
// console.log(reverseString('hello')); // 'olleh'
|
|
2559
3128
|
/**
|
|
2560
|
-
* Counts the number of words in a string.
|
|
2561
|
-
*
|
|
2562
|
-
* @
|
|
3129
|
+
* Counts the number of words in a string, based on whitespace separation.
|
|
3130
|
+
*
|
|
3131
|
+
* @param {string} str - The input string.
|
|
3132
|
+
* @returns {number} The number of words in the string.
|
|
2563
3133
|
*/
|
|
2564
3134
|
function countWords(str) {
|
|
2565
3135
|
return str.trim().split(/\s+/).length;
|
|
@@ -2567,9 +3137,10 @@ function countWords(str) {
|
|
|
2567
3137
|
// Example usage:
|
|
2568
3138
|
// console.log(countWords('Hello world!')); // 2
|
|
2569
3139
|
/**
|
|
2570
|
-
* Truncates a string to
|
|
2571
|
-
*
|
|
2572
|
-
* @param {
|
|
3140
|
+
* Truncates a string to a specified maximum length, appending '...' if the string is cut.
|
|
3141
|
+
*
|
|
3142
|
+
* @param {string} str - The input string.
|
|
3143
|
+
* @param {number} maxLength - The maximum desired length of the string.
|
|
2573
3144
|
* @returns {string} The truncated string.
|
|
2574
3145
|
*/
|
|
2575
3146
|
function truncateString(str, maxLength) {
|
|
@@ -2578,9 +3149,10 @@ function truncateString(str, maxLength) {
|
|
|
2578
3149
|
// Example usage:
|
|
2579
3150
|
// console.log(truncateString('This is a long string', 10)); // 'This is a...'
|
|
2580
3151
|
/**
|
|
2581
|
-
* Converts a string
|
|
2582
|
-
*
|
|
2583
|
-
* @
|
|
3152
|
+
* Converts a string from various cases (e.g., snake_case, kebab-case, space separated) into camelCase.
|
|
3153
|
+
*
|
|
3154
|
+
* @param {string} str - The input string.
|
|
3155
|
+
* @returns {string} The camelCase version of the string.
|
|
2584
3156
|
*/
|
|
2585
3157
|
function toCamelCase(str) {
|
|
2586
3158
|
return str
|
|
@@ -2590,9 +3162,10 @@ function toCamelCase(str) {
|
|
|
2590
3162
|
// Example usage:
|
|
2591
3163
|
// console.log(toCamelCase('hello world example')); // 'helloWorldExample'
|
|
2592
3164
|
/**
|
|
2593
|
-
* Converts a string
|
|
2594
|
-
*
|
|
2595
|
-
* @
|
|
3165
|
+
* Converts a string from various cases (e.g., camelCase, PascalCase, space separated) into kebab-case.
|
|
3166
|
+
*
|
|
3167
|
+
* @param {string} str - The input string.
|
|
3168
|
+
* @returns {string} The kebab-case version of the string.
|
|
2596
3169
|
*/
|
|
2597
3170
|
function toKebabCase(str) {
|
|
2598
3171
|
return str
|
|
@@ -2603,11 +3176,12 @@ function toKebabCase(str) {
|
|
|
2603
3176
|
// Example usage:
|
|
2604
3177
|
// console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
|
|
2605
3178
|
/**
|
|
2606
|
-
* Replaces all
|
|
2607
|
-
*
|
|
2608
|
-
* @param {string}
|
|
2609
|
-
* @param {string}
|
|
2610
|
-
* @
|
|
3179
|
+
* Replaces all occurrences of a substring with a new substring.
|
|
3180
|
+
*
|
|
3181
|
+
* @param {string} str - The original string.
|
|
3182
|
+
* @param {string} find - The substring to find and replace.
|
|
3183
|
+
* @param {string} replace - The substring to replace with.
|
|
3184
|
+
* @returns {string} A new string with all replacements made.
|
|
2611
3185
|
*/
|
|
2612
3186
|
function replaceAll(str, find, replace) {
|
|
2613
3187
|
return str.split(find).join(replace);
|
|
@@ -2615,9 +3189,10 @@ function replaceAll(str, find, replace) {
|
|
|
2615
3189
|
// Example usage:
|
|
2616
3190
|
// console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
|
|
2617
3191
|
/**
|
|
2618
|
-
* Generates a random string of a
|
|
2619
|
-
*
|
|
2620
|
-
* @
|
|
3192
|
+
* Generates a random alphanumeric string of a specified length.
|
|
3193
|
+
*
|
|
3194
|
+
* @param {number} length - The desired length of the random string.
|
|
3195
|
+
* @returns {string} The generated random string.
|
|
2621
3196
|
*/
|
|
2622
3197
|
function generateRandomString(length) {
|
|
2623
3198
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
@@ -2627,9 +3202,10 @@ function generateRandomString(length) {
|
|
|
2627
3202
|
// console.log(generateRandomString(10)); // 'A1b2C3d4E5'
|
|
2628
3203
|
|
|
2629
3204
|
/**
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
* @
|
|
3205
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not a letter or a specific special character.
|
|
3206
|
+
*
|
|
3207
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3208
|
+
* @returns {boolean} `true` if the key is valid, `false` otherwise.
|
|
2633
3209
|
*/
|
|
2634
3210
|
function validateLetters(e) {
|
|
2635
3211
|
const key = e.keyCode;
|
|
@@ -2660,9 +3236,10 @@ function validateLetters(e) {
|
|
|
2660
3236
|
// Example usage:
|
|
2661
3237
|
// document.addEventListener('keydown', validateLetters);
|
|
2662
3238
|
/**
|
|
2663
|
-
*
|
|
2664
|
-
*
|
|
2665
|
-
* @
|
|
3239
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not an alphanumeric character.
|
|
3240
|
+
*
|
|
3241
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3242
|
+
* @returns {boolean} `true` if the key is valid, `false` otherwise.
|
|
2666
3243
|
*/
|
|
2667
3244
|
function validateAlphanumeric(e) {
|
|
2668
3245
|
const key = e.keyCode;
|
|
@@ -2683,9 +3260,10 @@ function validateAlphanumeric(e) {
|
|
|
2683
3260
|
// Example usage:
|
|
2684
3261
|
// document.addEventListener('keydown', validateAlphanumeric);
|
|
2685
3262
|
/**
|
|
2686
|
-
*
|
|
2687
|
-
*
|
|
2688
|
-
* @
|
|
3263
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not a number.
|
|
3264
|
+
*
|
|
3265
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3266
|
+
* @returns {boolean} `true` if the key is a number, otherwise `false`.
|
|
2689
3267
|
*/
|
|
2690
3268
|
function validateNumbers(e) {
|
|
2691
3269
|
const key = e.keyCode;
|
|
@@ -2698,9 +3276,10 @@ function validateNumbers(e) {
|
|
|
2698
3276
|
// Example usage:
|
|
2699
3277
|
// document.addEventListener('keydown', validateNumbers);
|
|
2700
3278
|
/**
|
|
2701
|
-
* Validates
|
|
2702
|
-
*
|
|
2703
|
-
* @
|
|
3279
|
+
* Validates a phone number against a regex for 10-digit numbers.
|
|
3280
|
+
*
|
|
3281
|
+
* @param {string} phoneNumber - The phone number to validate.
|
|
3282
|
+
* @returns {boolean} `true` if the phone number is valid, otherwise `false`.
|
|
2704
3283
|
*/
|
|
2705
3284
|
function isValidPhoneNumber(phoneNumber) {
|
|
2706
3285
|
const phonePattern = /^[0-9]{10}$/; // Example pattern for 10-digit phone numbers
|
|
@@ -2710,9 +3289,10 @@ function isValidPhoneNumber(phoneNumber) {
|
|
|
2710
3289
|
// console.log(isValidPhoneNumber('1234567890')); // true
|
|
2711
3290
|
// console.log(isValidPhoneNumber('123-456-7890')); // false
|
|
2712
3291
|
/**
|
|
2713
|
-
* Validates
|
|
2714
|
-
*
|
|
2715
|
-
* @
|
|
3292
|
+
* Validates an email address against a standard regex pattern.
|
|
3293
|
+
*
|
|
3294
|
+
* @param {string} email - The email address to validate.
|
|
3295
|
+
* @returns {boolean} `true` if the email is valid, otherwise `false`.
|
|
2716
3296
|
*/
|
|
2717
3297
|
function isValidEmail(email) {
|
|
2718
3298
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -2722,9 +3302,10 @@ function isValidEmail(email) {
|
|
|
2722
3302
|
// console.log(isValidEmail('example@domain.com')); // true
|
|
2723
3303
|
// console.log(isValidEmail('invalid-email')); // false
|
|
2724
3304
|
/**
|
|
2725
|
-
* Validates
|
|
2726
|
-
*
|
|
2727
|
-
* @
|
|
3305
|
+
* Validates a string to see if it is a well-formed URL.
|
|
3306
|
+
*
|
|
3307
|
+
* @param {string} url - The URL string to validate.
|
|
3308
|
+
* @returns {boolean} `true` if the URL is valid, otherwise `false`.
|
|
2728
3309
|
*/
|
|
2729
3310
|
function isValidURL(url) {
|
|
2730
3311
|
try {
|
|
@@ -2739,9 +3320,10 @@ function isValidURL(url) {
|
|
|
2739
3320
|
// console.log(isValidURL('https://www.example.com')); // true
|
|
2740
3321
|
// console.log(isValidURL('invalid-url')); // false
|
|
2741
3322
|
/**
|
|
2742
|
-
* Validates
|
|
2743
|
-
*
|
|
2744
|
-
* @
|
|
3323
|
+
* Validates a string to ensure it is a valid date in 'YYYY-MM-DD' format.
|
|
3324
|
+
*
|
|
3325
|
+
* @param {string} date - The date string to validate.
|
|
3326
|
+
* @returns {boolean} `true` if the date is valid, otherwise `false`.
|
|
2745
3327
|
*/
|
|
2746
3328
|
function isValidDate(date) {
|
|
2747
3329
|
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -2755,9 +3337,10 @@ function isValidDate(date) {
|
|
|
2755
3337
|
// console.log(isValidDate('2024-08-31')); // true
|
|
2756
3338
|
// console.log(isValidDate('2024-02-30')); // false
|
|
2757
3339
|
/**
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2760
|
-
* @
|
|
3340
|
+
* Checks if a password meets a set of strength requirements (minimum length, uppercase, lowercase, numbers, special characters).
|
|
3341
|
+
*
|
|
3342
|
+
* @param {string} password - The password to validate.
|
|
3343
|
+
* @returns {boolean} `true` if the password is strong, otherwise `false`.
|
|
2761
3344
|
*/
|
|
2762
3345
|
function isStrongPassword(password) {
|
|
2763
3346
|
const minLength = 8;
|
|
@@ -2771,9 +3354,10 @@ function isStrongPassword(password) {
|
|
|
2771
3354
|
// console.log(isStrongPassword('Strong1@password')); // true
|
|
2772
3355
|
// console.log(isStrongPassword('weakpass')); // false
|
|
2773
3356
|
/**
|
|
2774
|
-
* Validates a credit card number using the Luhn algorithm.
|
|
2775
|
-
*
|
|
2776
|
-
* @
|
|
3357
|
+
* Validates a credit card number using the Luhn algorithm (mod-10 check).
|
|
3358
|
+
*
|
|
3359
|
+
* @param {string} cardNumber - The credit card number to validate.
|
|
3360
|
+
* @returns {boolean} `true` if the credit card number is valid, otherwise `false`.
|
|
2777
3361
|
*/
|
|
2778
3362
|
function isValidCreditCard(cardNumber) {
|
|
2779
3363
|
const sanitized = cardNumber.replace(/\D/g, '');
|
|
@@ -2795,9 +3379,10 @@ function isValidCreditCard(cardNumber) {
|
|
|
2795
3379
|
// console.log(isValidCreditCard('4111111111111111')); // true
|
|
2796
3380
|
// console.log(isValidCreditCard('1234567812345670')); // false
|
|
2797
3381
|
/**
|
|
2798
|
-
* Validates
|
|
2799
|
-
*
|
|
2800
|
-
* @
|
|
3382
|
+
* Validates a string to check if it's a valid 3- or 6-digit hexadecimal color code.
|
|
3383
|
+
*
|
|
3384
|
+
* @param {string} color - The hex color string to validate.
|
|
3385
|
+
* @returns {boolean} `true` if the color code is valid, otherwise `false`.
|
|
2801
3386
|
*/
|
|
2802
3387
|
function isValidHexColor(color) {
|
|
2803
3388
|
const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
|
|
@@ -2808,9 +3393,10 @@ function isValidHexColor(color) {
|
|
|
2808
3393
|
// console.log(isValidHexColor('#FFF')); // true
|
|
2809
3394
|
// console.log(isValidHexColor('#12345G')); // false
|
|
2810
3395
|
/**
|
|
2811
|
-
* Validates
|
|
2812
|
-
*
|
|
2813
|
-
* @
|
|
3396
|
+
* Validates a string to ensure it represents a valid time in 24-hour HH:MM format.
|
|
3397
|
+
*
|
|
3398
|
+
* @param {string} time - The time string to validate.
|
|
3399
|
+
* @returns {boolean} `true` if the time is valid, otherwise `false`.
|
|
2814
3400
|
*/
|
|
2815
3401
|
function isValidTime(time) {
|
|
2816
3402
|
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
@@ -2820,9 +3406,10 @@ function isValidTime(time) {
|
|
|
2820
3406
|
// console.log(isValidTime('14:30')); // true
|
|
2821
3407
|
// console.log(isValidTime('25:00')); // false
|
|
2822
3408
|
/**
|
|
2823
|
-
* Validates
|
|
2824
|
-
*
|
|
2825
|
-
* @
|
|
3409
|
+
* Validates a string to check if it is a valid IPv4 address.
|
|
3410
|
+
*
|
|
3411
|
+
* @param {string} ip - The IP address string to validate.
|
|
3412
|
+
* @returns {boolean} `true` if the IP address is valid, otherwise `false`.
|
|
2826
3413
|
*/
|
|
2827
3414
|
function isValidIP(ip) {
|
|
2828
3415
|
const ipPattern = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/;
|
|
@@ -2832,9 +3419,10 @@ function isValidIP(ip) {
|
|
|
2832
3419
|
// console.log(isValidIP('192.168.1.1')); // true
|
|
2833
3420
|
// console.log(isValidIP('999.999.999.999')); // false
|
|
2834
3421
|
/**
|
|
2835
|
-
* Validates
|
|
2836
|
-
*
|
|
2837
|
-
* @
|
|
3422
|
+
* Validates a string to check if it matches the format of a U.S. Social Security Number (SSN).
|
|
3423
|
+
*
|
|
3424
|
+
* @param {string} ssn - The SSN string to validate.
|
|
3425
|
+
* @returns {boolean} `true` if the SSN format is valid, otherwise `false`.
|
|
2838
3426
|
*/
|
|
2839
3427
|
function isValidSSN(ssn) {
|
|
2840
3428
|
const ssnPattern = /^\d{3}-\d{2}-\d{4}$/;
|
|
@@ -2844,9 +3432,10 @@ function isValidSSN(ssn) {
|
|
|
2844
3432
|
// console.log(isValidSSN('123-45-6789')); // true
|
|
2845
3433
|
// console.log(isValidSSN('123-45-678')); // false
|
|
2846
3434
|
/**
|
|
2847
|
-
* Validates
|
|
2848
|
-
*
|
|
2849
|
-
* @
|
|
3435
|
+
* Validates a string to check if it is a valid 5-digit or 9-digit (ZIP+4) U.S. ZIP code.
|
|
3436
|
+
*
|
|
3437
|
+
* @param {string} zip - The ZIP code string to validate.
|
|
3438
|
+
* @returns {boolean} `true` if the ZIP code is valid, otherwise `false`.
|
|
2850
3439
|
*/
|
|
2851
3440
|
function isValidZIP(zip) {
|
|
2852
3441
|
const zipPattern = /^\d{5}(-\d{4})?$/;
|
|
@@ -2857,9 +3446,10 @@ function isValidZIP(zip) {
|
|
|
2857
3446
|
// console.log(isValidZIP('12345-6789')); // true
|
|
2858
3447
|
// console.log(isValidZIP('1234')); // false
|
|
2859
3448
|
/**
|
|
2860
|
-
* Validates
|
|
2861
|
-
*
|
|
2862
|
-
* @
|
|
3449
|
+
* Validates a credit card expiry date string (MM/YY format) to ensure it is a valid, non-expired date.
|
|
3450
|
+
*
|
|
3451
|
+
* @param {string} expiryDate - The expiry date to validate.
|
|
3452
|
+
* @returns {boolean} `true` if the expiry date is valid and not in the past, otherwise `false`.
|
|
2863
3453
|
*/
|
|
2864
3454
|
function isValidExpiryDate(expiryDate) {
|
|
2865
3455
|
const expiryPattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
|
|
@@ -2874,9 +3464,10 @@ function isValidExpiryDate(expiryDate) {
|
|
|
2874
3464
|
// console.log(isValidExpiryDate('08/24')); // true
|
|
2875
3465
|
// console.log(isValidExpiryDate('12/22')); // false
|
|
2876
3466
|
/**
|
|
2877
|
-
* Validates
|
|
2878
|
-
*
|
|
2879
|
-
* @
|
|
3467
|
+
* Validates a string to check if it's a valid 8-digit hexadecimal color code (with alpha channel).
|
|
3468
|
+
*
|
|
3469
|
+
* @param {string} color - The hex color string to validate.
|
|
3470
|
+
* @returns {boolean} `true` if the color code is valid, otherwise `false`.
|
|
2880
3471
|
*/
|
|
2881
3472
|
function isValidHexColorAlpha(color) {
|
|
2882
3473
|
const hexPattern = /^#([0-9A-Fa-f]{8})$/;
|
|
@@ -2886,9 +3477,10 @@ function isValidHexColorAlpha(color) {
|
|
|
2886
3477
|
// console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
|
|
2887
3478
|
// console.log(isValidHexColorAlpha('#FFFFFF')); // false
|
|
2888
3479
|
/**
|
|
2889
|
-
* Validates
|
|
2890
|
-
*
|
|
2891
|
-
* @
|
|
3480
|
+
* Validates a username to ensure it contains only alphanumeric characters and underscores, with a length between 3 and 16 characters.
|
|
3481
|
+
*
|
|
3482
|
+
* @param {string} username - The username to validate.
|
|
3483
|
+
* @returns {boolean} `true` if the username is valid, otherwise `false`.
|
|
2892
3484
|
*/
|
|
2893
3485
|
function isValidUsername(username) {
|
|
2894
3486
|
const usernamePattern = /^[a-zA-Z0-9_]{3,16}$/; // 3 to 16 characters, letters, numbers, and underscores only
|
|
@@ -2898,9 +3490,10 @@ function isValidUsername(username) {
|
|
|
2898
3490
|
// console.log(isValidUsername('user_name123')); // true
|
|
2899
3491
|
// console.log(isValidUsername('us')); // false
|
|
2900
3492
|
/**
|
|
2901
|
-
* Validates
|
|
2902
|
-
*
|
|
2903
|
-
* @
|
|
3493
|
+
* Validates a string to ensure it represents a plausible human age (0-120).
|
|
3494
|
+
*
|
|
3495
|
+
* @param {string} age - The age string to validate.
|
|
3496
|
+
* @returns {boolean} `true` if the age is valid, otherwise `false`.
|
|
2904
3497
|
*/
|
|
2905
3498
|
function isValidAge(age) {
|
|
2906
3499
|
const ageNumber = parseInt(age, 10);
|
|
@@ -2910,9 +3503,10 @@ function isValidAge(age) {
|
|
|
2910
3503
|
// console.log(isValidAge('25')); // true
|
|
2911
3504
|
// console.log(isValidAge('121')); // false
|
|
2912
3505
|
/**
|
|
2913
|
-
* Validates
|
|
2914
|
-
*
|
|
2915
|
-
* @
|
|
3506
|
+
* Validates a string to check if it contains only valid hexadecimal characters.
|
|
3507
|
+
*
|
|
3508
|
+
* @param {string} hex - The string to validate.
|
|
3509
|
+
* @returns {boolean} `true` if the string is a valid hexadecimal number, otherwise `false`.
|
|
2916
3510
|
*/
|
|
2917
3511
|
function isValidHexNumber(hex) {
|
|
2918
3512
|
const hexPattern = /^[0-9A-Fa-f]+$/;
|
|
@@ -2922,73 +3516,211 @@ function isValidHexNumber(hex) {
|
|
|
2922
3516
|
// console.log(isValidHexNumber('1A3F')); // true
|
|
2923
3517
|
// console.log(isValidHexNumber('GHIJ')); // false
|
|
2924
3518
|
|
|
3519
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
3520
|
+
retries: 3,
|
|
3521
|
+
retryDelay: 1000,
|
|
3522
|
+
maxRetryDelay: 10000,
|
|
3523
|
+
backoffMultiplier: 2,
|
|
3524
|
+
retryCondition: (error) => {
|
|
3525
|
+
if (error instanceof NetworkError || error instanceof ServerError) {
|
|
3526
|
+
const statusCode = error.statusCode;
|
|
3527
|
+
if (!statusCode)
|
|
3528
|
+
return true;
|
|
3529
|
+
if (statusCode >= 500)
|
|
3530
|
+
return true;
|
|
3531
|
+
if (statusCode === 408 || statusCode === 429)
|
|
3532
|
+
return true;
|
|
3533
|
+
return false;
|
|
3534
|
+
}
|
|
3535
|
+
if (error instanceof Error) {
|
|
3536
|
+
return error.message.includes('timeout') || error.message.includes('network');
|
|
3537
|
+
}
|
|
3538
|
+
return false;
|
|
3539
|
+
},
|
|
3540
|
+
};
|
|
3541
|
+
async function retryWithBackoff(fn, config = {}) {
|
|
3542
|
+
const finalConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
3543
|
+
let lastError;
|
|
3544
|
+
let delay = finalConfig.retryDelay;
|
|
3545
|
+
for (let attempt = 0; attempt <= finalConfig.retries; attempt++) {
|
|
3546
|
+
try {
|
|
3547
|
+
return await fn();
|
|
3548
|
+
}
|
|
3549
|
+
catch (error) {
|
|
3550
|
+
lastError = error;
|
|
3551
|
+
if (attempt === finalConfig.retries) {
|
|
3552
|
+
break;
|
|
3553
|
+
}
|
|
3554
|
+
if (!finalConfig.retryCondition(error)) {
|
|
3555
|
+
throw error;
|
|
3556
|
+
}
|
|
3557
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3558
|
+
delay = Math.min(delay * finalConfig.backoffMultiplier, finalConfig.maxRetryDelay);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
throw lastError;
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
/**
|
|
3565
|
+
* A standardized RESTful class that provides a generic interface for performing
|
|
3566
|
+
* CRUD (Create, Read, Update, Delete) operations on a specific API resource. It is designed
|
|
3567
|
+
* to be extended directly from your models. It supports both JSON and FormData requests.
|
|
3568
|
+
*
|
|
3569
|
+
* @example
|
|
3570
|
+
* ```typescript
|
|
3571
|
+
* export class Role extends RestStd {
|
|
3572
|
+
* static override resource = 'roles';
|
|
3573
|
+
* static fetchFn = createAxiosFetcher(axiosInstance);
|
|
3574
|
+
* }
|
|
3575
|
+
*
|
|
3576
|
+
* const roles = await Role.getAll();
|
|
3577
|
+
* ```
|
|
3578
|
+
*/
|
|
2925
3579
|
class RestStd {
|
|
3580
|
+
/**
|
|
3581
|
+
* The resource endpoint. MUST be overridden in subclasses.
|
|
3582
|
+
* @example static override resource = 'users';
|
|
3583
|
+
*/
|
|
2926
3584
|
static resource;
|
|
3585
|
+
/** A flag to determine if request data should be sent as FormData. Defaults to `false`. */
|
|
2927
3586
|
static isFormData = false;
|
|
3587
|
+
/** A record of global headers to be sent with every request. */
|
|
2928
3588
|
static headers = {};
|
|
3589
|
+
/** The function used to make the actual HTTP requests. Optional, defaults to Axios fetcher. */
|
|
2929
3590
|
static fetchFn;
|
|
3591
|
+
/** Retry configuration for failed requests. Optional. */
|
|
3592
|
+
static retryConfig;
|
|
2930
3593
|
/**
|
|
2931
|
-
*
|
|
2932
|
-
* @
|
|
3594
|
+
* Validates that the resource property is defined.
|
|
3595
|
+
* @throws {Error} If resource is not defined
|
|
3596
|
+
*/
|
|
3597
|
+
static validateResource() {
|
|
3598
|
+
if (!this.resource || this.resource.trim() === '') {
|
|
3599
|
+
throw new Error(`[${this.constructor.name}] Static property 'resource' is required. ` +
|
|
3600
|
+
`Please define: static override resource = 'your-resource';`);
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Gets the fetcher function, using default if not provided.
|
|
3605
|
+
* Creates a default Axios fetcher if not configured, allowing lazy initialization.
|
|
3606
|
+
* @returns The fetcher function to use
|
|
3607
|
+
*/
|
|
3608
|
+
static getFetchFn() {
|
|
3609
|
+
if (this.fetchFn) {
|
|
3610
|
+
return this.fetchFn;
|
|
3611
|
+
}
|
|
3612
|
+
const axiosInstance = getConfiguredAxiosInstance();
|
|
3613
|
+
return createAxiosFetcher(axiosInstance);
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Executes a fetch request with optional retry logic.
|
|
3617
|
+
* @param config - The fetcher configuration
|
|
3618
|
+
* @returns A promise that resolves with the response data
|
|
3619
|
+
*/
|
|
3620
|
+
static async executeFetch(config) {
|
|
3621
|
+
const fetcher = this.getFetchFn();
|
|
3622
|
+
if (this.retryConfig) {
|
|
3623
|
+
return retryWithBackoff(() => fetcher(config), this.retryConfig);
|
|
3624
|
+
}
|
|
3625
|
+
try {
|
|
3626
|
+
return await fetcher(config);
|
|
3627
|
+
}
|
|
3628
|
+
catch (error) {
|
|
3629
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
3630
|
+
throw NetworkError.fromAxiosError(error);
|
|
3631
|
+
}
|
|
3632
|
+
if (error instanceof Error && error.name === 'TypeError' && error.message?.includes('fetch')) {
|
|
3633
|
+
throw NetworkError.fromFetchError(error);
|
|
3634
|
+
}
|
|
3635
|
+
throw error;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
/**
|
|
3639
|
+
* Builds a URL by combining base URL and suffix.
|
|
3640
|
+
* @param baseUrl - The base URL
|
|
3641
|
+
* @param suffix - Optional suffix to append
|
|
3642
|
+
* @returns The combined URL
|
|
3643
|
+
*/
|
|
3644
|
+
static buildUrl(baseUrl, suffix) {
|
|
3645
|
+
const cleanBase = baseUrl.replace(/\/$/, '');
|
|
3646
|
+
if (suffix) {
|
|
3647
|
+
const cleanSuffix = suffix.startsWith('/') ? suffix : `/${suffix}`;
|
|
3648
|
+
return cleanBase + cleanSuffix;
|
|
3649
|
+
}
|
|
3650
|
+
return cleanBase;
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Sets global headers that will be included in all subsequent requests made by this class.
|
|
3654
|
+
* @param headers - An object containing the headers to be set
|
|
2933
3655
|
*/
|
|
2934
3656
|
static setHeaders(headers) {
|
|
2935
3657
|
this.headers = { ...this.headers, ...headers };
|
|
2936
3658
|
}
|
|
2937
3659
|
/**
|
|
2938
|
-
*
|
|
2939
|
-
* @param data
|
|
2940
|
-
* @returns
|
|
3660
|
+
* Conditionally transforms the request data to FormData if `isFormData` is true.
|
|
3661
|
+
* @param data - The data to be potentially transformed
|
|
3662
|
+
* @returns The transformed data as FormData, or the original data
|
|
2941
3663
|
*/
|
|
2942
3664
|
static transformData(data) {
|
|
2943
3665
|
if (this.isFormData) {
|
|
2944
|
-
|
|
2945
|
-
return formData;
|
|
3666
|
+
return objectToFormData(data);
|
|
2946
3667
|
}
|
|
2947
3668
|
return data;
|
|
2948
3669
|
}
|
|
2949
3670
|
/**
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
2952
|
-
* @
|
|
2953
|
-
* @param options
|
|
2954
|
-
* @returns
|
|
3671
|
+
* Fetches a list of items from the resource's endpoint.
|
|
3672
|
+
* @template TResponse The expected response type
|
|
3673
|
+
* @template TParams The type of query parameters
|
|
3674
|
+
* @param options - Options including params, options, and optional url override
|
|
3675
|
+
* @returns A promise that resolves with the response data
|
|
2955
3676
|
*/
|
|
2956
|
-
static getAll(
|
|
2957
|
-
|
|
3677
|
+
static getAll(options = {}) {
|
|
3678
|
+
this.validateResource();
|
|
3679
|
+
const { params, url } = options;
|
|
3680
|
+
const finalUrl = url || this.resource;
|
|
3681
|
+
const config = {
|
|
2958
3682
|
method: "GET",
|
|
2959
|
-
url:
|
|
3683
|
+
url: finalUrl,
|
|
2960
3684
|
params,
|
|
2961
3685
|
headers: this.headers,
|
|
2962
|
-
}
|
|
3686
|
+
};
|
|
3687
|
+
return this.executeFetch(config);
|
|
2963
3688
|
}
|
|
2964
3689
|
/**
|
|
2965
|
-
*
|
|
2966
|
-
*
|
|
2967
|
-
* @
|
|
2968
|
-
* @param params
|
|
2969
|
-
* @
|
|
2970
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3690
|
+
* Fetches a single item by its ID.
|
|
3691
|
+
* @template TResponse The expected response type
|
|
3692
|
+
* @template TParams The type of query parameters
|
|
3693
|
+
* @param options - Options including id, params, options, and optional url override
|
|
3694
|
+
* @returns A promise that resolves with the response data
|
|
2971
3695
|
*/
|
|
2972
|
-
static getOne(
|
|
2973
|
-
|
|
3696
|
+
static getOne(options) {
|
|
3697
|
+
this.validateResource();
|
|
3698
|
+
const { id, params, url } = options;
|
|
3699
|
+
const baseUrl = url || this.resource;
|
|
3700
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3701
|
+
const config = {
|
|
2974
3702
|
method: "GET",
|
|
2975
|
-
url:
|
|
3703
|
+
url: finalUrl,
|
|
2976
3704
|
params,
|
|
2977
3705
|
headers: this.headers,
|
|
2978
|
-
}
|
|
3706
|
+
};
|
|
3707
|
+
return this.executeFetch(config);
|
|
2979
3708
|
}
|
|
2980
3709
|
/**
|
|
2981
|
-
*
|
|
2982
|
-
*
|
|
2983
|
-
* @
|
|
2984
|
-
* @param options
|
|
2985
|
-
* @returns
|
|
3710
|
+
* Creates a new item.
|
|
3711
|
+
* @template TResponse The expected response type
|
|
3712
|
+
* @template TData The type of data to send
|
|
3713
|
+
* @param options - Options including data, options, and optional url override
|
|
3714
|
+
* @returns A promise that resolves with the response data
|
|
2986
3715
|
*/
|
|
2987
|
-
static create(
|
|
3716
|
+
static create(options) {
|
|
3717
|
+
this.validateResource();
|
|
3718
|
+
const { data, url } = options;
|
|
3719
|
+
const finalUrl = url || this.resource;
|
|
2988
3720
|
const transformedData = this.transformData(data);
|
|
2989
|
-
|
|
3721
|
+
const config = {
|
|
2990
3722
|
method: "POST",
|
|
2991
|
-
url:
|
|
3723
|
+
url: finalUrl,
|
|
2992
3724
|
data: transformedData,
|
|
2993
3725
|
headers: {
|
|
2994
3726
|
...this.headers,
|
|
@@ -2996,22 +3728,27 @@ class RestStd {
|
|
|
2996
3728
|
? ContentTypeEnum.FORM_DATA
|
|
2997
3729
|
: ContentTypeEnum.JSON,
|
|
2998
3730
|
},
|
|
2999
|
-
}
|
|
3731
|
+
};
|
|
3732
|
+
return this.executeFetch(config);
|
|
3000
3733
|
}
|
|
3001
3734
|
/**
|
|
3002
|
-
*
|
|
3003
|
-
*
|
|
3004
|
-
* @
|
|
3005
|
-
* @param options
|
|
3006
|
-
* @returns
|
|
3735
|
+
* Creates multiple new items in a single request.
|
|
3736
|
+
* @template TResponse The expected response type
|
|
3737
|
+
* @template TData The type of data items to send
|
|
3738
|
+
* @param options - Options including data array, options, and optional url override
|
|
3739
|
+
* @returns A promise that resolves with the response data
|
|
3007
3740
|
*/
|
|
3008
|
-
static bulkCreate(
|
|
3741
|
+
static bulkCreate(options) {
|
|
3742
|
+
this.validateResource();
|
|
3743
|
+
const { data, url } = options;
|
|
3744
|
+
const baseUrl = url || this.resource;
|
|
3745
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3009
3746
|
const transformedData = this.isFormData
|
|
3010
3747
|
? data.map((item) => this.transformData(item))
|
|
3011
3748
|
: data;
|
|
3012
|
-
|
|
3749
|
+
const config = {
|
|
3013
3750
|
method: "POST",
|
|
3014
|
-
url:
|
|
3751
|
+
url: finalUrl,
|
|
3015
3752
|
data: transformedData,
|
|
3016
3753
|
headers: {
|
|
3017
3754
|
...this.headers,
|
|
@@ -3019,21 +3756,25 @@ class RestStd {
|
|
|
3019
3756
|
? ContentTypeEnum.FORM_DATA
|
|
3020
3757
|
: ContentTypeEnum.JSON,
|
|
3021
3758
|
},
|
|
3022
|
-
}
|
|
3759
|
+
};
|
|
3760
|
+
return this.executeFetch(config);
|
|
3023
3761
|
}
|
|
3024
3762
|
/**
|
|
3025
|
-
*
|
|
3026
|
-
*
|
|
3027
|
-
* @
|
|
3028
|
-
* @param
|
|
3029
|
-
* @
|
|
3030
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3763
|
+
* Updates an existing item by its ID.
|
|
3764
|
+
* @template TResponse The expected response type
|
|
3765
|
+
* @template TData The type of data to send
|
|
3766
|
+
* @param options - Options including id, data, options, and optional url override
|
|
3767
|
+
* @returns A promise that resolves with the response data
|
|
3031
3768
|
*/
|
|
3032
|
-
static update(
|
|
3769
|
+
static update(options) {
|
|
3770
|
+
this.validateResource();
|
|
3771
|
+
const { id, data, url } = options;
|
|
3772
|
+
const baseUrl = url || this.resource;
|
|
3773
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3033
3774
|
const transformedData = this.transformData(data);
|
|
3034
|
-
|
|
3775
|
+
const config = {
|
|
3035
3776
|
method: "PUT",
|
|
3036
|
-
url:
|
|
3777
|
+
url: finalUrl,
|
|
3037
3778
|
data: transformedData,
|
|
3038
3779
|
headers: {
|
|
3039
3780
|
...this.headers,
|
|
@@ -3041,22 +3782,27 @@ class RestStd {
|
|
|
3041
3782
|
? ContentTypeEnum.FORM_DATA
|
|
3042
3783
|
: ContentTypeEnum.JSON,
|
|
3043
3784
|
},
|
|
3044
|
-
}
|
|
3785
|
+
};
|
|
3786
|
+
return this.executeFetch(config);
|
|
3045
3787
|
}
|
|
3046
3788
|
/**
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
* @
|
|
3050
|
-
* @param options
|
|
3051
|
-
* @returns
|
|
3789
|
+
* Updates multiple existing items in a single request.
|
|
3790
|
+
* @template TResponse The expected response type
|
|
3791
|
+
* @template TData The type of data items to send
|
|
3792
|
+
* @param options - Options including data array, options, and optional url override
|
|
3793
|
+
* @returns A promise that resolves with the response data
|
|
3052
3794
|
*/
|
|
3053
|
-
static bulkUpdate(
|
|
3795
|
+
static bulkUpdate(options) {
|
|
3796
|
+
this.validateResource();
|
|
3797
|
+
const { data, url } = options;
|
|
3798
|
+
const baseUrl = url || this.resource;
|
|
3799
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3054
3800
|
const transformedData = this.isFormData
|
|
3055
3801
|
? data.map((item) => this.transformData(item))
|
|
3056
3802
|
: data;
|
|
3057
|
-
|
|
3803
|
+
const config = {
|
|
3058
3804
|
method: "PUT",
|
|
3059
|
-
url:
|
|
3805
|
+
url: finalUrl,
|
|
3060
3806
|
data: transformedData,
|
|
3061
3807
|
headers: {
|
|
3062
3808
|
...this.headers,
|
|
@@ -3064,21 +3810,25 @@ class RestStd {
|
|
|
3064
3810
|
? ContentTypeEnum.FORM_DATA
|
|
3065
3811
|
: ContentTypeEnum.JSON,
|
|
3066
3812
|
},
|
|
3067
|
-
}
|
|
3813
|
+
};
|
|
3814
|
+
return this.executeFetch(config);
|
|
3068
3815
|
}
|
|
3069
3816
|
/**
|
|
3070
|
-
* Partially
|
|
3071
|
-
*
|
|
3072
|
-
* @
|
|
3073
|
-
* @param
|
|
3074
|
-
* @
|
|
3075
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3817
|
+
* Partially updates an existing item by its ID.
|
|
3818
|
+
* @template TResponse The expected response type
|
|
3819
|
+
* @template TData The type of data to send (partial)
|
|
3820
|
+
* @param options - Options including id, data, options, and optional url override
|
|
3821
|
+
* @returns A promise that resolves with the response data
|
|
3076
3822
|
*/
|
|
3077
|
-
static patch(
|
|
3823
|
+
static patch(options) {
|
|
3824
|
+
this.validateResource();
|
|
3825
|
+
const { id, data, url } = options;
|
|
3826
|
+
const baseUrl = url || this.resource;
|
|
3827
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3078
3828
|
const transformedData = this.transformData(data);
|
|
3079
|
-
|
|
3829
|
+
const config = {
|
|
3080
3830
|
method: "PATCH",
|
|
3081
|
-
url:
|
|
3831
|
+
url: finalUrl,
|
|
3082
3832
|
data: transformedData,
|
|
3083
3833
|
headers: {
|
|
3084
3834
|
...this.headers,
|
|
@@ -3086,65 +3836,80 @@ class RestStd {
|
|
|
3086
3836
|
? ContentTypeEnum.FORM_DATA
|
|
3087
3837
|
: ContentTypeEnum.JSON,
|
|
3088
3838
|
},
|
|
3089
|
-
}
|
|
3839
|
+
};
|
|
3840
|
+
return this.executeFetch(config);
|
|
3090
3841
|
}
|
|
3091
3842
|
/**
|
|
3092
|
-
*
|
|
3093
|
-
*
|
|
3094
|
-
* @param
|
|
3095
|
-
* @
|
|
3096
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3843
|
+
* Deletes an item by its ID.
|
|
3844
|
+
* @template TResponse The expected response type
|
|
3845
|
+
* @param options - Options including id, options, and optional url override
|
|
3846
|
+
* @returns A promise that resolves with the response data
|
|
3097
3847
|
*/
|
|
3098
|
-
static delete(
|
|
3099
|
-
|
|
3848
|
+
static delete(options) {
|
|
3849
|
+
this.validateResource();
|
|
3850
|
+
const { id, url } = options;
|
|
3851
|
+
const baseUrl = url || this.resource;
|
|
3852
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3853
|
+
const config = {
|
|
3100
3854
|
method: "DELETE",
|
|
3101
|
-
url:
|
|
3855
|
+
url: finalUrl,
|
|
3102
3856
|
headers: this.headers,
|
|
3103
|
-
}
|
|
3857
|
+
};
|
|
3858
|
+
return this.executeFetch(config);
|
|
3104
3859
|
}
|
|
3105
3860
|
/**
|
|
3106
|
-
*
|
|
3107
|
-
*
|
|
3108
|
-
* @param
|
|
3109
|
-
* @
|
|
3110
|
-
* @returns The result of the fetch function.
|
|
3861
|
+
* Deletes multiple items by their IDs in a single request.
|
|
3862
|
+
* @template TResponse The expected response type
|
|
3863
|
+
* @param options - Options including ids array, options, and optional url override
|
|
3864
|
+
* @returns A promise that resolves with the response data
|
|
3111
3865
|
*/
|
|
3112
|
-
static bulkDelete(
|
|
3113
|
-
|
|
3866
|
+
static bulkDelete(options) {
|
|
3867
|
+
this.validateResource();
|
|
3868
|
+
const { ids, url } = options;
|
|
3869
|
+
const baseUrl = url || this.resource;
|
|
3870
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3871
|
+
const config = {
|
|
3114
3872
|
method: "DELETE",
|
|
3115
|
-
url:
|
|
3873
|
+
url: finalUrl,
|
|
3116
3874
|
data: { ids },
|
|
3117
3875
|
headers: this.headers,
|
|
3118
|
-
}
|
|
3876
|
+
};
|
|
3877
|
+
return this.executeFetch(config);
|
|
3119
3878
|
}
|
|
3120
3879
|
/**
|
|
3121
|
-
*
|
|
3122
|
-
*
|
|
3123
|
-
* @
|
|
3124
|
-
* @param options
|
|
3125
|
-
* @returns
|
|
3880
|
+
* Creates a new item or updates an existing one, based on the presence of an `id` property in the data.
|
|
3881
|
+
* @template TResponse The expected response type
|
|
3882
|
+
* @template TData The type of data to send (must have optional id)
|
|
3883
|
+
* @param options - Options including data, options, and optional url override
|
|
3884
|
+
* @returns A promise that resolves with the response data
|
|
3126
3885
|
*/
|
|
3127
|
-
static upsert(
|
|
3128
|
-
if (data.id) {
|
|
3129
|
-
return this.update(
|
|
3886
|
+
static upsert(options) {
|
|
3887
|
+
if (options.data.id) {
|
|
3888
|
+
return this.update({
|
|
3889
|
+
id: options.data.id,
|
|
3890
|
+
data: options.data,
|
|
3891
|
+
url: options.url,
|
|
3892
|
+
});
|
|
3130
3893
|
}
|
|
3131
3894
|
else {
|
|
3132
|
-
return this.create(
|
|
3895
|
+
return this.create({
|
|
3896
|
+
data: options.data,
|
|
3897
|
+
url: options.url,
|
|
3898
|
+
});
|
|
3133
3899
|
}
|
|
3134
3900
|
}
|
|
3135
3901
|
/**
|
|
3136
|
-
*
|
|
3137
|
-
*
|
|
3138
|
-
* @
|
|
3139
|
-
* @
|
|
3140
|
-
* @param params
|
|
3141
|
-
* @
|
|
3142
|
-
* @param options Additional options for the fetch function.
|
|
3143
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3902
|
+
* Makes a custom HTTP request, providing full flexibility over the method, URL, and data.
|
|
3903
|
+
* @template TResponse The expected response type
|
|
3904
|
+
* @template TParams The type of query parameters
|
|
3905
|
+
* @template TData The type of request body data
|
|
3906
|
+
* @param options - Options including method, url, params, data, and options
|
|
3907
|
+
* @returns A promise that resolves with the response data
|
|
3144
3908
|
*/
|
|
3145
|
-
static customRequest(
|
|
3909
|
+
static customRequest(options) {
|
|
3910
|
+
const { method, url, params, data } = options;
|
|
3146
3911
|
const transformedData = this.transformData(data);
|
|
3147
|
-
|
|
3912
|
+
const config = {
|
|
3148
3913
|
method: method,
|
|
3149
3914
|
url: url,
|
|
3150
3915
|
params: params,
|
|
@@ -3155,17 +3920,20 @@ class RestStd {
|
|
|
3155
3920
|
? ContentTypeEnum.FORM_DATA
|
|
3156
3921
|
: ContentTypeEnum.JSON,
|
|
3157
3922
|
},
|
|
3158
|
-
}
|
|
3923
|
+
};
|
|
3924
|
+
return this.executeFetch(config);
|
|
3159
3925
|
}
|
|
3160
3926
|
}
|
|
3161
3927
|
|
|
3162
3928
|
/**
|
|
3163
|
-
*
|
|
3929
|
+
* A composable function that executes an Axios request and returns the response data.
|
|
3930
|
+
* It simplifies making API calls by wrapping the Axios request in a reusable function.
|
|
3164
3931
|
*
|
|
3165
|
-
* @template T The expected
|
|
3166
|
-
* @param {AxiosInstance} axios - The Axios instance for making
|
|
3167
|
-
* @param {AxiosRequestConfig} axiosRequest -
|
|
3168
|
-
* @returns {Promise<T>} A promise with the
|
|
3932
|
+
* @template T The expected type of the response data.
|
|
3933
|
+
* @param {AxiosInstance} axios - The Axios instance to use for making the request.
|
|
3934
|
+
* @param {AxiosRequestConfig} axiosRequest - The configuration for the Axios request (e.g., URL, method, headers).
|
|
3935
|
+
* @returns {Promise<T>} A promise that resolves with the data from the Axios response.
|
|
3936
|
+
* @throws {Error} Throws an error if the Axios request fails.
|
|
3169
3937
|
*/
|
|
3170
3938
|
async function axiosFetch(axios, axiosRequest) {
|
|
3171
3939
|
return axios(axiosRequest)
|
|
@@ -3176,10 +3944,19 @@ async function axiosFetch(axios, axiosRequest) {
|
|
|
3176
3944
|
}
|
|
3177
3945
|
|
|
3178
3946
|
/**
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3181
|
-
*
|
|
3182
|
-
*
|
|
3947
|
+
* A factory function that creates a reusable query function for making API requests.
|
|
3948
|
+
* It abstracts the Axios instance creation and allows for a custom instance to be provided.
|
|
3949
|
+
* This is particularly useful for creating typed query functions for use with libraries like Vue Query.
|
|
3950
|
+
*
|
|
3951
|
+
* @template TQueryFnData The expected data type of the query function's response.
|
|
3952
|
+
* @template TData The expected data type of the transformed data.
|
|
3953
|
+
* @param {Function} fetchFn - The underlying function that will be called to perform the fetch operation.
|
|
3954
|
+
* This function should accept an Axios instance, Axios request configuration, and optional query options.
|
|
3955
|
+
* @param {AxiosInstance} [axiosCustomInstance] - An optional custom Axios instance to use for the request.
|
|
3956
|
+
* If not provided, a default configured instance will be used.
|
|
3957
|
+
* @returns {(axiosRequestConfig: AxiosRequestConfig, options?: UseQueryOptions<TQueryFnData, Error, TData>) => any}
|
|
3958
|
+
* A new function that takes Axios request configuration and optional query options, and when executed,
|
|
3959
|
+
* performs the API request using the configured `fetchFn`.
|
|
3183
3960
|
*/
|
|
3184
3961
|
function useFetch(fetchFn, axiosCustomInstance) {
|
|
3185
3962
|
const instance = axiosCustomInstance || getConfiguredAxiosInstance();
|
|
@@ -3188,131 +3965,20 @@ function useFetch(fetchFn, axiosCustomInstance) {
|
|
|
3188
3965
|
};
|
|
3189
3966
|
}
|
|
3190
3967
|
|
|
3191
|
-
/**
|
|
3192
|
-
* Custom composable for integrating Axios requests with Vue Query.
|
|
3193
|
-
*
|
|
3194
|
-
* @template T - The type of data expected from the query.
|
|
3195
|
-
* @param {AxiosInstance} axios - Axios instance used for making HTTP requests.
|
|
3196
|
-
* @param {AxiosRequestConfig} axiosRequest - Initial Axios request configuration.
|
|
3197
|
-
* @param {ExtendedQueryOptions<T>} [queryOptions] - Optional Vue Query options with server execution flag.
|
|
3198
|
-
* @returns {object} Composable functions and query state for managing API requests.
|
|
3199
|
-
*/
|
|
3200
|
-
function useVueQuery(axios, axiosRequest, queryOptions) {
|
|
3201
|
-
const key = ref([
|
|
3202
|
-
queryOptions?.queryKey || `${axiosRequest.url}-${JSON.stringify(axiosRequest)}`
|
|
3203
|
-
]);
|
|
3204
|
-
const axiosRequestRef = ref(axiosRequest);
|
|
3205
|
-
const queryOptionsRef = ref(queryOptions);
|
|
3206
|
-
const isInitialized = ref(false);
|
|
3207
|
-
const errorCallbacks = ref([]);
|
|
3208
|
-
const resultCallbacks = ref([]);
|
|
3209
|
-
/**
|
|
3210
|
-
* Vue Query instance for managing API requests.
|
|
3211
|
-
*/
|
|
3212
|
-
const query = useQuery({
|
|
3213
|
-
queryKey: key.value,
|
|
3214
|
-
queryFn: async () => {
|
|
3215
|
-
const response = await axios(axiosRequestRef.value);
|
|
3216
|
-
return response.data;
|
|
3217
|
-
},
|
|
3218
|
-
...(queryOptionsRef.value?.options ?? {})
|
|
3219
|
-
});
|
|
3220
|
-
/**
|
|
3221
|
-
* Executes the query manually with optional new key and request parameters.
|
|
3222
|
-
*
|
|
3223
|
-
* @param {Array<any>} [newKey] - New query key to use.
|
|
3224
|
-
* @param {object} [newRequestParams] - New request parameters to use.
|
|
3225
|
-
* @param {ExtendedQueryOptions<T>} [newQueryOptions] - New Vue Query options to use.
|
|
3226
|
-
*/
|
|
3227
|
-
function execute(newKey, newRequestParams, newQueryOptions) {
|
|
3228
|
-
if (newKey) {
|
|
3229
|
-
key.value = newKey;
|
|
3230
|
-
}
|
|
3231
|
-
if (newRequestParams) {
|
|
3232
|
-
axiosRequestRef.value.data = newRequestParams;
|
|
3233
|
-
}
|
|
3234
|
-
if (newQueryOptions) {
|
|
3235
|
-
queryOptionsRef.value = newQueryOptions;
|
|
3236
|
-
}
|
|
3237
|
-
query.refetch();
|
|
3238
|
-
}
|
|
3239
|
-
/**
|
|
3240
|
-
* Registers a callback to be executed on successful request result.
|
|
3241
|
-
* If data is already available, the callback will be executed immediately.
|
|
3242
|
-
*
|
|
3243
|
-
* @param {(data: T) => void} cb - Callback function to handle the result.
|
|
3244
|
-
*/
|
|
3245
|
-
function onResult(cb) {
|
|
3246
|
-
resultCallbacks.value.push(cb);
|
|
3247
|
-
if (query.data.value !== undefined && query.data.value !== null) {
|
|
3248
|
-
cb(query.data.value);
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
/**
|
|
3252
|
-
* Registers a callback to be executed when the request results in an error.
|
|
3253
|
-
* If an error is already present, the callback will be executed immediately.
|
|
3254
|
-
*
|
|
3255
|
-
* @param {(e: unknown) => void} callback - Callback function to handle errors.
|
|
3256
|
-
*/
|
|
3257
|
-
function onError(callback) {
|
|
3258
|
-
errorCallbacks.value.push(callback);
|
|
3259
|
-
if (query.error.value !== undefined) {
|
|
3260
|
-
callback(query.error.value);
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
/**
|
|
3264
|
-
* Watches for changes in the query result and triggers result callbacks.
|
|
3265
|
-
*/
|
|
3266
|
-
watch(query.data, (newData) => {
|
|
3267
|
-
if (newData !== undefined && newData !== null && isInitialized.value) {
|
|
3268
|
-
resultCallbacks.value.forEach((cb) => cb(newData));
|
|
3269
|
-
}
|
|
3270
|
-
});
|
|
3271
|
-
/**
|
|
3272
|
-
* Watches for changes in query errors and triggers error callbacks.
|
|
3273
|
-
*/
|
|
3274
|
-
watch(query.error, (newError) => {
|
|
3275
|
-
if (newError !== undefined && isInitialized.value) {
|
|
3276
|
-
errorCallbacks.value.forEach((cb) => cb(newError));
|
|
3277
|
-
}
|
|
3278
|
-
});
|
|
3279
|
-
/**
|
|
3280
|
-
* Handles server-side prefetching for SSR.
|
|
3281
|
-
* Ensures data is fetched before rendering on the server.
|
|
3282
|
-
*/
|
|
3283
|
-
onServerPrefetch(async () => {
|
|
3284
|
-
if (queryOptionsRef.value?.server !== false) {
|
|
3285
|
-
await query.suspense();
|
|
3286
|
-
if (query.data.value != null) {
|
|
3287
|
-
resultCallbacks.value.forEach((cb) => cb(query.data.value));
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
});
|
|
3291
|
-
/**
|
|
3292
|
-
* Ensures callbacks are executed after the component is mounted on the client-side.
|
|
3293
|
-
*/
|
|
3294
|
-
onMounted(() => {
|
|
3295
|
-
isInitialized.value = true;
|
|
3296
|
-
if (query.data.value != null) {
|
|
3297
|
-
resultCallbacks.value.forEach((cb) => cb(query.data.value));
|
|
3298
|
-
}
|
|
3299
|
-
if (query.error.value !== undefined) {
|
|
3300
|
-
errorCallbacks.value.forEach((cb) => cb(query.error.value));
|
|
3301
|
-
}
|
|
3302
|
-
});
|
|
3303
|
-
return {
|
|
3304
|
-
execute,
|
|
3305
|
-
onResult,
|
|
3306
|
-
onError,
|
|
3307
|
-
...query
|
|
3308
|
-
};
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
3968
|
let notified = false;
|
|
3312
3969
|
/**
|
|
3313
|
-
*
|
|
3970
|
+
* A composable that provides a reactive interface to Tailwind CSS breakpoints using `@vueuse/core`.
|
|
3971
|
+
* It simplifies working with responsive layouts by offering a set of reactive booleans for different
|
|
3972
|
+
* screen sizes and combinations. On its first invocation, it also logs the current device type (Mobile,
|
|
3973
|
+
* Tablet, Laptop, or Desktop) to the console for easier debugging during development.
|
|
3314
3974
|
*
|
|
3315
|
-
* @returns {
|
|
3975
|
+
* @returns {object} An object containing various reactive properties for screen sizes, window dimensions, and breakpoint utilities, including:
|
|
3976
|
+
* - `current`: A ref to the current breakpoint name.
|
|
3977
|
+
* - `active`: A ref to the currently active breakpoint name.
|
|
3978
|
+
* - `sm_S`, `md_GE`, etc.: A series of refs indicating if the screen is smaller than, greater than or equal to, or between specific breakpoints.
|
|
3979
|
+
* - `mobile`, `tablet`, `laptop`, `desktop`: Refs that are true for common device width ranges.
|
|
3980
|
+
* - `windowWidth`, `windowHeight`: Reactive refs for the window's width and height.
|
|
3981
|
+
* - `breakpoints`: The original `useBreakpoints` return object from `@vueuse/core`.
|
|
3316
3982
|
*/
|
|
3317
3983
|
function useBreakpoint() {
|
|
3318
3984
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
@@ -3424,15 +4090,20 @@ function useBreakpoint() {
|
|
|
3424
4090
|
}
|
|
3425
4091
|
|
|
3426
4092
|
/**
|
|
3427
|
-
* A composable function that filters objects based on a
|
|
4093
|
+
* A composable function that filters an array of objects based on a specified field, data type, and criteria.
|
|
4094
|
+
* It supports filtering by date range, string matching (case-insensitive and diacritic-insensitive), number range, and boolean values.
|
|
3428
4095
|
*
|
|
3429
|
-
* @
|
|
3430
|
-
* @param {
|
|
3431
|
-
* @param {
|
|
3432
|
-
* @param {string} filterConfig.
|
|
3433
|
-
* @param {
|
|
3434
|
-
*
|
|
3435
|
-
*
|
|
4096
|
+
* @template T A generic type that extends a record of string keys to any value, representing the objects in the array.
|
|
4097
|
+
* @param {T[]} items - The array of objects to be filtered.
|
|
4098
|
+
* @param {object} filterConfig - The configuration object for filtering.
|
|
4099
|
+
* @param {string} filterConfig.field - The name of the field in the objects to filter by.
|
|
4100
|
+
* @param {'date' | 'string' | 'number' | 'boolean'} filterConfig.type - The data type of the field to be filtered.
|
|
4101
|
+
* @param {any} filterConfig.criteria - The criteria for filtering, which varies based on the `type`:
|
|
4102
|
+
* - For 'date': An object `{ startDate: string, endDate: string }`.
|
|
4103
|
+
* - For 'string': A string to search for.
|
|
4104
|
+
* - For 'number': An object `{ min: number, max: number }`.
|
|
4105
|
+
* - For 'boolean': A boolean value.
|
|
4106
|
+
* @returns {T[]} The filtered array of objects. If the criteria are invalid or not provided, the original array is returned.
|
|
3436
4107
|
*/
|
|
3437
4108
|
function useFilter(items, filterConfig) {
|
|
3438
4109
|
// Ensure the filterConfig is correctly structured
|
|
@@ -3481,12 +4152,20 @@ function useFilter(items, filterConfig) {
|
|
|
3481
4152
|
}
|
|
3482
4153
|
|
|
3483
4154
|
/**
|
|
3484
|
-
*
|
|
4155
|
+
* A composable that provides pagination logic based on reactive refs for the current page,
|
|
4156
|
+
* total number of items, and items per page.
|
|
3485
4157
|
*
|
|
3486
|
-
* @param page -
|
|
3487
|
-
* @param total -
|
|
3488
|
-
* @param pageSize -
|
|
3489
|
-
* @returns
|
|
4158
|
+
* @param {Ref<number>} page - A reactive ref representing the current page number.
|
|
4159
|
+
* @param {Ref<number>} total - A reactive ref representing the total number of items to be paginated.
|
|
4160
|
+
* @param {Ref<number>} pageSize - A reactive ref representing the number of items per page.
|
|
4161
|
+
* @returns {{
|
|
4162
|
+
* totalPages: import('vue').ComputedRef<number>,
|
|
4163
|
+
* canFetchNextPage: () => boolean,
|
|
4164
|
+
* canFetchPreviousPage: () => boolean
|
|
4165
|
+
* }} An object containing:
|
|
4166
|
+
* - `totalPages`: A computed property that calculates the total number of pages.
|
|
4167
|
+
* - `canFetchNextPage`: A function that returns `true` if there is a next page.
|
|
4168
|
+
* - `canFetchPreviousPage`: A function that returns `true` if there is a previous page.
|
|
3490
4169
|
*/
|
|
3491
4170
|
function usePagination(page, total, pageSize) {
|
|
3492
4171
|
// Calculate the total number of pages
|
|
@@ -3503,11 +4182,16 @@ function usePagination(page, total, pageSize) {
|
|
|
3503
4182
|
}
|
|
3504
4183
|
|
|
3505
4184
|
/**
|
|
3506
|
-
*
|
|
3507
|
-
*
|
|
3508
|
-
*
|
|
3509
|
-
* @
|
|
3510
|
-
* @
|
|
4185
|
+
* A composable that sorts an array of objects based on a selected criterion from a list of predefined sorting options.
|
|
4186
|
+
* It supports sorting by number, date, boolean, and string fields, in both ascending and descending order.
|
|
4187
|
+
*
|
|
4188
|
+
* @template T The type of items in the array.
|
|
4189
|
+
* @param {T[]} items - The array of objects to sort.
|
|
4190
|
+
* @param {Array<{value: number, label: string, field: string, order: string, type: string}>} criteriaList - A list of
|
|
4191
|
+
* sorting criteria objects. Each object defines a sorting option with a unique `value`, a `label` for display, the `field`
|
|
4192
|
+
* to sort by, the `order` ('asc' or 'desc'), and the data `type` ('number', 'date', 'boolean', 'string').
|
|
4193
|
+
* @param {number} selectedCriteria - The `value` of the currently selected sorting criterion from the `criteriaList`.
|
|
4194
|
+
* @returns {ComputedRef<T[]>} A computed ref containing the sorted items. If the selected criterion is not found, the original array is returned.
|
|
3511
4195
|
*/
|
|
3512
4196
|
function useSorter(items, criteriaList, selectedCriteria) {
|
|
3513
4197
|
return computed(() => {
|
|
@@ -3539,36 +4223,58 @@ function useSorter(items, criteriaList, selectedCriteria) {
|
|
|
3539
4223
|
return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
|
|
3540
4224
|
}
|
|
3541
4225
|
});
|
|
3542
|
-
})
|
|
4226
|
+
});
|
|
3543
4227
|
}
|
|
3544
4228
|
|
|
3545
4229
|
/**
|
|
3546
4230
|
* Custom hook for authentication logic, including login, logout, token management, and session preference.
|
|
4231
|
+
* Accepts an optional fetcher function. If not provided, uses the default configured fetcher or falls back to Axios.
|
|
4232
|
+
*
|
|
4233
|
+
* @param {Fetcher} [fetcher] - Optional fetcher function to use for auth requests. If not provided, uses the default configured fetcher.
|
|
4234
|
+
* @returns {{
|
|
4235
|
+
* logout: (params?: Record<string, unknown>) => Promise<void>,
|
|
4236
|
+
* login: (params: Record<string, unknown>, persistence: LocationPreference, tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>
|
|
4237
|
+
* }} An object containing authentication functions.
|
|
3547
4238
|
*
|
|
3548
|
-
* @
|
|
3549
|
-
*
|
|
4239
|
+
* @example
|
|
4240
|
+
* ```typescript
|
|
4241
|
+
* // Using default fetcher (Axios)
|
|
4242
|
+
* const auth = useAuth();
|
|
4243
|
+
*
|
|
4244
|
+
* // Using custom fetcher
|
|
4245
|
+
* const customFetcher = createOfetchFetcher();
|
|
4246
|
+
* const auth = useAuth(customFetcher);
|
|
4247
|
+
* ```
|
|
3550
4248
|
*/
|
|
3551
|
-
function useAuth() {
|
|
3552
|
-
const axiosInstance = getConfiguredAxiosInstance();
|
|
4249
|
+
function useAuth(fetcher) {
|
|
3553
4250
|
const endpoints = getEndpointsConfig();
|
|
4251
|
+
const getFetcher = () => {
|
|
4252
|
+
return fetcher || getDefaultAuthFetcher();
|
|
4253
|
+
};
|
|
3554
4254
|
/**
|
|
3555
4255
|
* Logs out the user by making a POST request to the logout endpoint,
|
|
3556
4256
|
* cleaning all stored credentials, and reloading the page.
|
|
3557
4257
|
* The session persistence preference is NOT reset here; it persists across logouts.
|
|
3558
4258
|
*
|
|
3559
|
-
* @param {
|
|
4259
|
+
* @param {Record<string, unknown>} [params={}] - Optional parameters to send with the logout request.
|
|
3560
4260
|
* @returns {Promise<void>}
|
|
3561
4261
|
*/
|
|
3562
4262
|
const logout = async (params = {}) => {
|
|
3563
4263
|
try {
|
|
3564
|
-
await
|
|
4264
|
+
await getFetcher()({
|
|
4265
|
+
method: 'POST',
|
|
4266
|
+
url: endpoints.LOGOUT,
|
|
4267
|
+
data: params,
|
|
4268
|
+
});
|
|
3565
4269
|
}
|
|
3566
4270
|
catch (error) {
|
|
3567
|
-
handleError(error
|
|
4271
|
+
handleError(error);
|
|
3568
4272
|
}
|
|
3569
4273
|
finally {
|
|
3570
4274
|
await cleanCredentials(await getSessionPersistence());
|
|
3571
|
-
window
|
|
4275
|
+
if (typeof window !== 'undefined') {
|
|
4276
|
+
window.location.reload();
|
|
4277
|
+
}
|
|
3572
4278
|
}
|
|
3573
4279
|
};
|
|
3574
4280
|
/**
|
|
@@ -3583,7 +4289,11 @@ function useAuth() {
|
|
|
3583
4289
|
*/
|
|
3584
4290
|
const login = async (params = {}, persistence, tokenPaths = getTokenPathsConfig()) => {
|
|
3585
4291
|
try {
|
|
3586
|
-
const
|
|
4292
|
+
const data = await getFetcher()({
|
|
4293
|
+
method: 'POST',
|
|
4294
|
+
url: endpoints.LOGIN,
|
|
4295
|
+
data: params,
|
|
4296
|
+
});
|
|
3587
4297
|
const { accessToken, refreshToken } = extractAndValidateTokens(data, tokenPaths, "LOGIN");
|
|
3588
4298
|
configSession({
|
|
3589
4299
|
persistencePreference: persistence,
|
|
@@ -3592,7 +4302,7 @@ function useAuth() {
|
|
|
3592
4302
|
return data;
|
|
3593
4303
|
}
|
|
3594
4304
|
catch (error) {
|
|
3595
|
-
handleError(error
|
|
4305
|
+
handleError(error);
|
|
3596
4306
|
throw error;
|
|
3597
4307
|
}
|
|
3598
4308
|
};
|
|
@@ -3602,17 +4312,63 @@ function useAuth() {
|
|
|
3602
4312
|
};
|
|
3603
4313
|
}
|
|
3604
4314
|
|
|
4315
|
+
// @ts-ignore - ofetch is an optional peer dependency
|
|
4316
|
+
/**
|
|
4317
|
+
* Creates a fetcher function using ofetch.
|
|
4318
|
+
*
|
|
4319
|
+
* @param baseURL - Optional base URL for requests
|
|
4320
|
+
* @param defaultOptions - Optional default options for ofetch
|
|
4321
|
+
* @returns A fetcher function compatible with RestStd
|
|
4322
|
+
*
|
|
4323
|
+
* @example
|
|
4324
|
+
* ```typescript
|
|
4325
|
+
* import { createOfetchFetcher, RestStd } from '@arex95/vue-core';
|
|
4326
|
+
*
|
|
4327
|
+
* export class Role extends RestStd {
|
|
4328
|
+
* static override resource = 'roles';
|
|
4329
|
+
* static fetchFn = createOfetchFetcher('https://api.example.com');
|
|
4330
|
+
* }
|
|
4331
|
+
* ```
|
|
4332
|
+
*
|
|
4333
|
+
* @example
|
|
4334
|
+
* ```typescript
|
|
4335
|
+
* import { createFetch } from 'ofetch';
|
|
4336
|
+
* import { createOfetchFetcher, RestStd } from '@arex95/vue-core';
|
|
4337
|
+
*
|
|
4338
|
+
* const ofetchInstance = createFetch({ baseURL: 'https://api.example.com' });
|
|
4339
|
+
*
|
|
4340
|
+
* export class Role extends RestStd {
|
|
4341
|
+
* static override resource = 'roles';
|
|
4342
|
+
* static fetchFn = createOfetchFetcher(undefined, { fetch: ofetchInstance });
|
|
4343
|
+
* }
|
|
4344
|
+
* ```
|
|
4345
|
+
*/
|
|
4346
|
+
function createOfetchFetcher(baseURL, defaultOptions) {
|
|
4347
|
+
return async (config) => {
|
|
4348
|
+
const url = baseURL
|
|
4349
|
+
? `${baseURL.replace(/\/$/, '')}/${config.url.replace(/^\//, '')}`
|
|
4350
|
+
: config.url;
|
|
4351
|
+
return $fetch(url, {
|
|
4352
|
+
method: config.method,
|
|
4353
|
+
query: config.params,
|
|
4354
|
+
body: config.data,
|
|
4355
|
+
headers: config.headers,
|
|
4356
|
+
...defaultOptions,
|
|
4357
|
+
});
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
|
|
3605
4361
|
/**
|
|
3606
|
-
*
|
|
3607
|
-
*
|
|
4362
|
+
* A Vue plugin that serves as the entry point for the `@arex95/vue-core` library.
|
|
4363
|
+
* It initializes and configures all the core modules, such as authentication, API communication,
|
|
4364
|
+
* and token management, based on the provided options.
|
|
3608
4365
|
*/
|
|
3609
4366
|
const ArexVueCore = {
|
|
3610
4367
|
/**
|
|
3611
|
-
* The `install` method
|
|
3612
|
-
* It is automatically called when `app.use(ArexVueCore, options)` is executed.
|
|
4368
|
+
* The `install` method required by Vue's plugin system. It is called when `app.use()` is invoked.
|
|
3613
4369
|
*
|
|
3614
|
-
* @param app The Vue application instance.
|
|
3615
|
-
* @param options The configuration
|
|
4370
|
+
* @param {App} app - The Vue application instance.
|
|
4371
|
+
* @param {ArexVueCoreOptions} options - The configuration object for the library.
|
|
3616
4372
|
*/
|
|
3617
4373
|
install: (app, options) => {
|
|
3618
4374
|
if (!options) {
|
|
@@ -3648,4 +4404,4 @@ const ArexVueCore = {
|
|
|
3648
4404
|
},
|
|
3649
4405
|
};
|
|
3650
4406
|
|
|
3651
|
-
export { AppTypes, ArchiveTypes, ArexVueCore, AudioTypes, AxiosService, ContentTypeEnum, DocumentTypes, ERROR_MESSAGES, ERROR_STYLES, ErrorEnum, ErrorMessages, ErrorStyles, ExceptionEnum, FontTypes, ImageTypes, KeyCodeEnum, OtherTypes, RestStd, ScreenBreakpoint, ScreenSize, StorageKeyEnum, StorageTypeEnum, TextTypes, VideoTypes, ab2hex, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, cleanCredentials, clickOutside, compareObject, configAppKey, configAxios, configEndpoints, configRefreshTokenPaths, configSession, configTokenKeys, configTokenPaths, copyToClipboard, countWords, createKeyMap, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, decrypt, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, encrypt, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, extractAndValidateTokens, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAppKey, getAuthRefreshToken, getAuthToken, getConfiguredAxiosInstance, getDecryptedItem, getEndOfMonth, getEndpointsConfig, getObjectDifferences, getObjectKeys, getQueryParam, getRefreshTokenPathsConfig, getSessionConfig, getSessionId, getSessionPersistence, getStartOfMonth, getTokenConfig, getTokenPathsConfig, handleError, hasNestedProperties, hex2ab, importKey, isEmptyObject, isLeapYear, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, refreshTokens, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, reverseString, safeGet, screenMap, scrollToTop, simulateKeyPress, stopDetectingKeyHold, storeAuthRefreshToken, storeAuthToken, storeEncryptedItem, storeTokens, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useAuth, useBreakpoint, useFetch, useFilter, usePagination, useSorter,
|
|
4407
|
+
export { AppTypes, ArchiveTypes, ArexVueCore, AudioTypes, AuthError, AxiosService, BaseError, ContentTypeEnum, DocumentTypes, ERROR_MESSAGES, ERROR_STYLES, ErrorEnum, ErrorMessages, ErrorStyles, ExceptionEnum, FontTypes, ImageTypes, KeyCodeEnum, NetworkError, OtherTypes, RestStd, ScreenBreakpoint, ScreenSize, ServerError, StorageKeyEnum, StorageTypeEnum, TextTypes, ValidationError, VideoTypes, ab2hex, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, cleanCredentials, clickOutside, compareObject, configAppKey, configAuthFetcher, configAxios, configEndpoints, configRefreshTokenPaths, configSession, configTokenKeys, configTokenPaths, copyToClipboard, countWords, createAxiosFetcher, createKeyMap, createOfetchFetcher, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, decrypt, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, encrypt, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, extractAndValidateTokens, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAppKey, getAuthRefreshToken, getAuthToken, getConfiguredAxiosInstance, getCookieStorage, getDecryptedItem, getDefaultAuthFetcher, getEndOfMonth, getEndpointsConfig, getObjectDifferences, getObjectKeys, getPreferredStorage, getQueryParam, getRefreshTokenPathsConfig, getSessionConfig, getSessionId, getSessionPersistence, getSessionStorage, getStartOfMonth, getStorage, getTokenConfig, getTokenPathsConfig, handleError, hasNestedProperties, hex2ab, importKey, isClient, isEmptyObject, isLeapYear, isServer, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, refreshTokens, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, retryWithBackoff, reverseString, safeGet, screenMap, scrollToTop, setDefaultAuthFetcherFactory, simulateKeyPress, stopDetectingKeyHold, storeAuthRefreshToken, storeAuthToken, storeEncryptedItem, storeTokens, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useAuth, useBreakpoint, useFetch, useFilter, usePagination, useSorter, validateAlphanumeric, validateLetters, validateNumbers, verifyAuth };
|