@arex95/vue-core 1.1.43 → 3.0.0
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 +14 -0
- 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 +1633 -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,113 @@ 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 to avoid circular dependency
|
|
1998
|
+
const { setDefaultAuthFetcherFactory } = require("@/config/auth/authFetcher");
|
|
1999
|
+
const { createAxiosFetcher } = require("@/fetchers/axios");
|
|
2000
|
+
setDefaultAuthFetcherFactory(() => {
|
|
2001
|
+
return createAxiosFetcher(axiosServiceInstance.getAxiosInstance());
|
|
2002
|
+
});
|
|
1536
2003
|
};
|
|
2004
|
+
/**
|
|
2005
|
+
* Retrieves the configured singleton Axios instance.
|
|
2006
|
+
* If not configured yet, creates a default instance with minimal configuration.
|
|
2007
|
+
* This allows lazy initialization to avoid dependency circular issues in Nuxt and other frameworks.
|
|
2008
|
+
*
|
|
2009
|
+
* @returns {AxiosInstance} The configured Axios instance.
|
|
2010
|
+
*/
|
|
1537
2011
|
const getConfiguredAxiosInstance = () => {
|
|
1538
2012
|
if (!axiosServiceInstance) {
|
|
1539
|
-
|
|
2013
|
+
if (defaultConfig) {
|
|
2014
|
+
axiosServiceInstance = new AxiosService({
|
|
2015
|
+
baseURL: defaultConfig.baseURL,
|
|
2016
|
+
headers: defaultConfig.headers,
|
|
2017
|
+
timeout: defaultConfig.timeout,
|
|
2018
|
+
withCredentials: defaultConfig.withCredentials
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
else {
|
|
2022
|
+
axiosServiceInstance = new AxiosService({
|
|
2023
|
+
baseURL: '',
|
|
2024
|
+
headers: {},
|
|
2025
|
+
timeout: 30000,
|
|
2026
|
+
withCredentials: false
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
// Configure auth fetcher factory to avoid circular dependency
|
|
2030
|
+
const { setDefaultAuthFetcherFactory } = require("@/config/auth/authFetcher");
|
|
2031
|
+
const { createAxiosFetcher } = require("@/fetchers/axios");
|
|
2032
|
+
setDefaultAuthFetcherFactory(() => {
|
|
2033
|
+
return createAxiosFetcher(axiosServiceInstance.getAxiosInstance());
|
|
2034
|
+
});
|
|
1540
2035
|
}
|
|
1541
2036
|
return axiosServiceInstance.getAxiosInstance();
|
|
1542
2037
|
};
|
|
1543
2038
|
|
|
1544
2039
|
/**
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
* @param {
|
|
2040
|
+
* Triggers a browser download for a file created from a `Blob` object.
|
|
2041
|
+
*
|
|
2042
|
+
* @param {Blob} blob - The `Blob` containing the file's data.
|
|
2043
|
+
* @param {string} fileName - The desired name for the downloaded file.
|
|
1548
2044
|
*/
|
|
1549
2045
|
function downloadFile(blob, fileName) {
|
|
1550
2046
|
const link = document.createElement('a');
|
|
@@ -1559,10 +2055,11 @@ function downloadFile(blob, fileName) {
|
|
|
1559
2055
|
URL.revokeObjectURL(url);
|
|
1560
2056
|
}
|
|
1561
2057
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
* @param {
|
|
1565
|
-
* @param {
|
|
2058
|
+
* Converts an array of data into a CSV format and triggers a download.
|
|
2059
|
+
*
|
|
2060
|
+
* @param {string[]} headers - An array of strings to be used as the CSV header row.
|
|
2061
|
+
* @param {any[][]} data - A 2D array representing the rows and cells of the data to be exported.
|
|
2062
|
+
* @param {string} fileName - The desired name for the downloaded CSV file.
|
|
1566
2063
|
*/
|
|
1567
2064
|
function exportToCSV(headers, data, fileName) {
|
|
1568
2065
|
const csvRows = [];
|
|
@@ -1579,10 +2076,11 @@ function exportToCSV(headers, data, fileName) {
|
|
|
1579
2076
|
downloadFile(blob, fileName);
|
|
1580
2077
|
}
|
|
1581
2078
|
/**
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1584
|
-
* @param {
|
|
1585
|
-
* @param {
|
|
2079
|
+
* Converts an array of data into an HTML table, then triggers a download as an Excel (.xls) file.
|
|
2080
|
+
*
|
|
2081
|
+
* @param {string[]} headers - An array of strings for the table headers.
|
|
2082
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2083
|
+
* @param {string} fileName - The desired name for the downloaded Excel file.
|
|
1586
2084
|
*/
|
|
1587
2085
|
function exportToExcel(headers, data, fileName) {
|
|
1588
2086
|
// Create a table element
|
|
@@ -1611,9 +2109,10 @@ function exportToExcel(headers, data, fileName) {
|
|
|
1611
2109
|
downloadFile(blob, fileName);
|
|
1612
2110
|
}
|
|
1613
2111
|
/**
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
* @param {
|
|
2112
|
+
* Converts an array of data into a pretty-printed JSON string and triggers a download.
|
|
2113
|
+
*
|
|
2114
|
+
* @param {any[]} data - The data to be serialized into JSON.
|
|
2115
|
+
* @param {string} fileName - The desired name for the downloaded JSON file.
|
|
1617
2116
|
*/
|
|
1618
2117
|
function exportToJSON(data, fileName) {
|
|
1619
2118
|
const jsonContent = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
|
|
@@ -1622,10 +2121,11 @@ function exportToJSON(data, fileName) {
|
|
|
1622
2121
|
downloadFile(blob, fileName);
|
|
1623
2122
|
}
|
|
1624
2123
|
/**
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
* @param {
|
|
1628
|
-
* @param {
|
|
2124
|
+
* Converts an array of data into a simple XML format and triggers a download.
|
|
2125
|
+
*
|
|
2126
|
+
* @param {string[]} headers - An array of strings to be used as column headers in the XML.
|
|
2127
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2128
|
+
* @param {string} fileName - The desired name for the downloaded XML file.
|
|
1629
2129
|
*/
|
|
1630
2130
|
function exportToXML(headers, data, fileName) {
|
|
1631
2131
|
let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<rows>\n';
|
|
@@ -1649,10 +2149,11 @@ function exportToXML(headers, data, fileName) {
|
|
|
1649
2149
|
downloadFile(blob, fileName);
|
|
1650
2150
|
}
|
|
1651
2151
|
/**
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1654
|
-
* @param {
|
|
1655
|
-
* @param {
|
|
2152
|
+
* Converts an array of data into a tab-separated text format and triggers a download.
|
|
2153
|
+
*
|
|
2154
|
+
* @param {string[]} headers - An array of strings for the header row.
|
|
2155
|
+
* @param {any[][]} data - A 2D array of the data to be exported.
|
|
2156
|
+
* @param {string} fileName - The desired name for the downloaded text file.
|
|
1656
2157
|
*/
|
|
1657
2158
|
function exportToText(headers, data, fileName) {
|
|
1658
2159
|
const textRows = [];
|
|
@@ -1670,12 +2171,13 @@ function exportToText(headers, data, fileName) {
|
|
|
1670
2171
|
}
|
|
1671
2172
|
|
|
1672
2173
|
/**
|
|
1673
|
-
* Opens a new window with
|
|
1674
|
-
*
|
|
1675
|
-
* @param {
|
|
1676
|
-
* @param {
|
|
1677
|
-
* @param {
|
|
1678
|
-
* @param {boolean} [opt.
|
|
2174
|
+
* Opens a new browser window or tab with a specified URL, providing options for the target and security attributes.
|
|
2175
|
+
*
|
|
2176
|
+
* @param {string} url - The URL to open in the new window.
|
|
2177
|
+
* @param {object} [opt] - Optional configuration for the new window.
|
|
2178
|
+
* @param {string} [opt.target='_blank'] - The target attribute for the link, specifying where to open the content (e.g., '_blank', '_self').
|
|
2179
|
+
* @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.
|
|
2180
|
+
* @param {boolean} [opt.noreferrer=true] - If `true`, adds `noreferrer` to prevent the browser from sending the `Referer` HTTP header.
|
|
1679
2181
|
*/
|
|
1680
2182
|
function openWindow(url, opt) {
|
|
1681
2183
|
const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
|
|
@@ -1689,9 +2191,11 @@ function openWindow(url, opt) {
|
|
|
1689
2191
|
window.open(url, target, features.join(','));
|
|
1690
2192
|
}
|
|
1691
2193
|
/**
|
|
1692
|
-
*
|
|
1693
|
-
*
|
|
1694
|
-
*
|
|
2194
|
+
* Asynchronously copies a given string to the user's clipboard. It uses the modern `navigator.clipboard` API
|
|
2195
|
+
* with a fallback to the deprecated `document.execCommand` for older browsers.
|
|
2196
|
+
*
|
|
2197
|
+
* @param {string} text - The string to be copied to the clipboard.
|
|
2198
|
+
* @returns {Promise<void>} A promise that resolves when the text has been successfully copied.
|
|
1695
2199
|
*/
|
|
1696
2200
|
async function copyToClipboard(text) {
|
|
1697
2201
|
if (navigator.clipboard) {
|
|
@@ -1708,8 +2212,10 @@ async function copyToClipboard(text) {
|
|
|
1708
2212
|
}
|
|
1709
2213
|
}
|
|
1710
2214
|
/**
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
2215
|
+
* Smoothly scrolls the window to the top of the page using a `requestAnimationFrame` loop
|
|
2216
|
+
* for a fluid animation.
|
|
2217
|
+
*
|
|
2218
|
+
* @param {number} [duration=300] - The total duration of the scroll animation in milliseconds.
|
|
1713
2219
|
*/
|
|
1714
2220
|
function scrollToTop(duration = 300) {
|
|
1715
2221
|
const start = window.scrollY;
|
|
@@ -1725,9 +2231,10 @@ function scrollToTop(duration = 300) {
|
|
|
1725
2231
|
requestAnimationFrame(scroll);
|
|
1726
2232
|
}
|
|
1727
2233
|
/**
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
* @
|
|
2234
|
+
* Retrieves the value of a specified query parameter from the current URL's search string.
|
|
2235
|
+
*
|
|
2236
|
+
* @param {string} paramName - The name of the query parameter to retrieve.
|
|
2237
|
+
* @returns {string | null} The value of the query parameter, or `null` if the parameter is not present in the URL.
|
|
1731
2238
|
*/
|
|
1732
2239
|
function getQueryParam(paramName) {
|
|
1733
2240
|
const urlParams = new URLSearchParams(window.location.search);
|
|
@@ -1735,7 +2242,7 @@ function getQueryParam(paramName) {
|
|
|
1735
2242
|
}
|
|
1736
2243
|
|
|
1737
2244
|
/**
|
|
1738
|
-
* Disables the right-click context menu on the window.
|
|
2245
|
+
* Disables the default right-click context menu on the entire window.
|
|
1739
2246
|
*/
|
|
1740
2247
|
function disableRightClick() {
|
|
1741
2248
|
const handler = (event) => event.preventDefault();
|
|
@@ -1745,7 +2252,7 @@ function disableRightClick() {
|
|
|
1745
2252
|
}
|
|
1746
2253
|
disableRightClick.handler = null;
|
|
1747
2254
|
/**
|
|
1748
|
-
*
|
|
2255
|
+
* Re-enables the right-click context menu if it was previously disabled by `disableRightClick`.
|
|
1749
2256
|
*/
|
|
1750
2257
|
function enableRightClick() {
|
|
1751
2258
|
if (disableRightClick.handler) {
|
|
@@ -1753,8 +2260,9 @@ function enableRightClick() {
|
|
|
1753
2260
|
}
|
|
1754
2261
|
}
|
|
1755
2262
|
/**
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
2263
|
+
* Prevents the default action for specific mouse buttons on the `mousedown` event.
|
|
2264
|
+
*
|
|
2265
|
+
* @param {number[]} buttons - An array of mouse button codes to disable (0 for left, 1 for middle, 2 for right).
|
|
1758
2266
|
*/
|
|
1759
2267
|
function disableMouseButtons(buttons) {
|
|
1760
2268
|
const handler = (event) => {
|
|
@@ -1769,7 +2277,7 @@ function disableMouseButtons(buttons) {
|
|
|
1769
2277
|
}
|
|
1770
2278
|
disableMouseButtons.handlers = [];
|
|
1771
2279
|
/**
|
|
1772
|
-
*
|
|
2280
|
+
* Re-enables all mouse buttons that were previously disabled by `disableMouseButtons`.
|
|
1773
2281
|
*/
|
|
1774
2282
|
function enableMouseButtons() {
|
|
1775
2283
|
for (const handler of disableMouseButtons.handlers) {
|
|
@@ -1779,17 +2287,19 @@ function enableMouseButtons() {
|
|
|
1779
2287
|
disableMouseButtons.handlers = [];
|
|
1780
2288
|
}
|
|
1781
2289
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
* @param {
|
|
2290
|
+
* Attaches a `dblclick` event listener to a specified HTML element.
|
|
2291
|
+
*
|
|
2292
|
+
* @param {HTMLElement} element - The DOM element to attach the listener to.
|
|
2293
|
+
* @param {(event: MouseEvent) => void} callback - The function to execute when the element is double-clicked.
|
|
1785
2294
|
*/
|
|
1786
2295
|
function addDoubleClickListener(element, callback) {
|
|
1787
2296
|
element.addEventListener('dblclick', callback);
|
|
1788
2297
|
}
|
|
1789
2298
|
/**
|
|
1790
|
-
* Removes a
|
|
1791
|
-
*
|
|
1792
|
-
* @param {
|
|
2299
|
+
* Removes a `dblclick` event listener from a specified HTML element.
|
|
2300
|
+
*
|
|
2301
|
+
* @param {HTMLElement} element - The DOM element to remove the listener from.
|
|
2302
|
+
* @param {(event: MouseEvent) => void} callback - The callback function that was originally added.
|
|
1793
2303
|
*/
|
|
1794
2304
|
function removeDoubleClickListener(element, callback) {
|
|
1795
2305
|
element.removeEventListener('dblclick', callback);
|
|
@@ -1797,9 +2307,10 @@ function removeDoubleClickListener(element, callback) {
|
|
|
1797
2307
|
// Example usage:
|
|
1798
2308
|
// addDoubleClickListener(document.body, () => alert('Double clicked!'));
|
|
1799
2309
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
* @param {
|
|
2310
|
+
* Sets up a global click listener to detect when a user clicks outside of a specified element.
|
|
2311
|
+
*
|
|
2312
|
+
* @param {HTMLElement} element - The element to monitor for outside clicks.
|
|
2313
|
+
* @param {() => void} callback - The function to execute when a click outside the element is detected.
|
|
1803
2314
|
*/
|
|
1804
2315
|
function clickOutside(element, callback) {
|
|
1805
2316
|
const handler = (event) => {
|
|
@@ -1813,8 +2324,9 @@ function clickOutside(element, callback) {
|
|
|
1813
2324
|
}
|
|
1814
2325
|
clickOutside.handlers = [];
|
|
1815
2326
|
/**
|
|
1816
|
-
* Removes the click outside listener for a specific element
|
|
1817
|
-
*
|
|
2327
|
+
* Removes the "click outside" event listener for a specific element that was added by `clickOutside`.
|
|
2328
|
+
*
|
|
2329
|
+
* @param {HTMLElement} element - The element for which to remove the listener.
|
|
1818
2330
|
*/
|
|
1819
2331
|
function removeClickOutside(element) {
|
|
1820
2332
|
const index = clickOutside.handlers.findIndex(h => h.element === element);
|
|
@@ -1829,7 +2341,8 @@ function removeClickOutside(element) {
|
|
|
1829
2341
|
// clickOutside(menu, () => menu.style.display = 'none');
|
|
1830
2342
|
// }
|
|
1831
2343
|
/**
|
|
1832
|
-
* Disables the F12 key and
|
|
2344
|
+
* Disables the F12 key and common developer tool shortcuts (Ctrl+Shift+I, Ctrl+Shift+J)
|
|
2345
|
+
* to prevent users from easily opening the browser's developer console.
|
|
1833
2346
|
*/
|
|
1834
2347
|
function disableF12Key() {
|
|
1835
2348
|
const handler = function (event) {
|
|
@@ -1838,8 +2351,9 @@ function disableF12Key() {
|
|
|
1838
2351
|
document.addEventListener('keydown', handler);
|
|
1839
2352
|
}
|
|
1840
2353
|
/**
|
|
1841
|
-
* Enables or disables the
|
|
1842
|
-
*
|
|
2354
|
+
* Enables or disables the ability to navigate through focusable elements using the Tab key.
|
|
2355
|
+
*
|
|
2356
|
+
* @param {boolean} enable - If `true`, tab navigation is enabled; if `false`, it is disabled.
|
|
1843
2357
|
*/
|
|
1844
2358
|
function toggleTabNavigation(enable) {
|
|
1845
2359
|
if (enable) {
|
|
@@ -1856,7 +2370,7 @@ function toggleTabNavigation(enable) {
|
|
|
1856
2370
|
}
|
|
1857
2371
|
}
|
|
1858
2372
|
/**
|
|
1859
|
-
*
|
|
2373
|
+
* Prevents users from copying content from the page by intercepting the `copy` event.
|
|
1860
2374
|
*/
|
|
1861
2375
|
function disableCopy() {
|
|
1862
2376
|
document.addEventListener('copy', (event) => {
|
|
@@ -1865,11 +2379,12 @@ function disableCopy() {
|
|
|
1865
2379
|
});
|
|
1866
2380
|
}
|
|
1867
2381
|
/**
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1870
|
-
* @param {
|
|
1871
|
-
* @param {
|
|
1872
|
-
* @param {boolean} [
|
|
2382
|
+
* Registers a global keyboard shortcut that triggers a callback when a specific key combination is pressed.
|
|
2383
|
+
*
|
|
2384
|
+
* @param {string} key - The main key for the shortcut (e.g., 'S', 'F1').
|
|
2385
|
+
* @param {() => void} callback - The function to execute when the shortcut is pressed.
|
|
2386
|
+
* @param {boolean} [ctrlKey=false] - If `true`, the Ctrl key must be pressed.
|
|
2387
|
+
* @param {boolean} [shiftKey=false] - If `true`, the Shift key must be pressed.
|
|
1873
2388
|
*/
|
|
1874
2389
|
function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = false) {
|
|
1875
2390
|
document.addEventListener('keydown', (event) => {
|
|
@@ -1882,10 +2397,11 @@ function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = fa
|
|
|
1882
2397
|
});
|
|
1883
2398
|
}
|
|
1884
2399
|
/**
|
|
1885
|
-
* Removes a
|
|
1886
|
-
*
|
|
1887
|
-
* @param {
|
|
1888
|
-
* @param {boolean} [
|
|
2400
|
+
* Removes a global keyboard shortcut that was previously added.
|
|
2401
|
+
*
|
|
2402
|
+
* @param {string} key - The main key of the shortcut to remove.
|
|
2403
|
+
* @param {boolean} [ctrlKey=false] - The Ctrl key modifier of the shortcut.
|
|
2404
|
+
* @param {boolean} [shiftKey=false] - The Shift key modifier of the shortcut.
|
|
1889
2405
|
*/
|
|
1890
2406
|
function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
|
|
1891
2407
|
const handler = (event) => {
|
|
@@ -1898,8 +2414,9 @@ function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
|
|
|
1898
2414
|
document.removeEventListener('keydown', handler);
|
|
1899
2415
|
}
|
|
1900
2416
|
/**
|
|
1901
|
-
* Disables
|
|
1902
|
-
*
|
|
2417
|
+
* Disables a list of specified keys or key combinations.
|
|
2418
|
+
*
|
|
2419
|
+
* @param {string[]} keys - An array of key names or combinations (e.g., 'F1', 'Control+S') to disable.
|
|
1903
2420
|
*/
|
|
1904
2421
|
function disableSpecificKeys(keys) {
|
|
1905
2422
|
const handler = (event) => {
|
|
@@ -1914,7 +2431,7 @@ function disableSpecificKeys(keys) {
|
|
|
1914
2431
|
}
|
|
1915
2432
|
disableSpecificKeys.handlers = [];
|
|
1916
2433
|
/**
|
|
1917
|
-
*
|
|
2434
|
+
* Re-enables all keys that were previously disabled by `disableSpecificKeys`.
|
|
1918
2435
|
*/
|
|
1919
2436
|
function enableSpecificKeys() {
|
|
1920
2437
|
for (const handler of disableSpecificKeys.handlers) {
|
|
@@ -1923,8 +2440,9 @@ function enableSpecificKeys() {
|
|
|
1923
2440
|
disableSpecificKeys.handlers = [];
|
|
1924
2441
|
}
|
|
1925
2442
|
/**
|
|
1926
|
-
* Registers multiple keyboard shortcuts
|
|
1927
|
-
*
|
|
2443
|
+
* Registers multiple keyboard shortcuts from an array of shortcut configurations.
|
|
2444
|
+
*
|
|
2445
|
+
* @param {Array<{ key: string; ctrlKey?: boolean; shiftKey?: boolean; altKey?: boolean; callback: () => void }>} shortcuts - An array of shortcut objects.
|
|
1928
2446
|
*/
|
|
1929
2447
|
function registerKeyboardShortcuts(shortcuts) {
|
|
1930
2448
|
const handler = (event) => {
|
|
@@ -1944,7 +2462,7 @@ function registerKeyboardShortcuts(shortcuts) {
|
|
|
1944
2462
|
}
|
|
1945
2463
|
registerKeyboardShortcuts.handlers = [];
|
|
1946
2464
|
/**
|
|
1947
|
-
*
|
|
2465
|
+
* Removes all keyboard shortcuts that were registered using `registerKeyboardShortcuts`.
|
|
1948
2466
|
*/
|
|
1949
2467
|
function unregisterKeyboardShortcuts() {
|
|
1950
2468
|
for (const handler of registerKeyboardShortcuts.handlers) {
|
|
@@ -1953,9 +2471,10 @@ function unregisterKeyboardShortcuts() {
|
|
|
1953
2471
|
registerKeyboardShortcuts.handlers = [];
|
|
1954
2472
|
}
|
|
1955
2473
|
/**
|
|
1956
|
-
* Adds a listener for a specific key
|
|
1957
|
-
*
|
|
1958
|
-
* @param {
|
|
2474
|
+
* Adds a global `keydown` listener for a specific key.
|
|
2475
|
+
*
|
|
2476
|
+
* @param {string} key - The key to listen for (e.g., 'Enter', 'Escape').
|
|
2477
|
+
* @param {() => void} callback - The function to execute when the key is pressed.
|
|
1959
2478
|
*/
|
|
1960
2479
|
function addKeyListener(key, callback) {
|
|
1961
2480
|
const handler = (event) => {
|
|
@@ -1970,7 +2489,7 @@ function addKeyListener(key, callback) {
|
|
|
1970
2489
|
}
|
|
1971
2490
|
addKeyListener.handlers = [];
|
|
1972
2491
|
/**
|
|
1973
|
-
* Removes all
|
|
2492
|
+
* Removes all key listeners that were added using `addKeyListener`.
|
|
1974
2493
|
*/
|
|
1975
2494
|
function removeKeyListeners() {
|
|
1976
2495
|
for (const handler of addKeyListener.handlers) {
|
|
@@ -1979,9 +2498,10 @@ function removeKeyListeners() {
|
|
|
1979
2498
|
addKeyListener.handlers = [];
|
|
1980
2499
|
}
|
|
1981
2500
|
/**
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
* @param {
|
|
2501
|
+
* Executes a callback function repeatedly while a specific key is held down.
|
|
2502
|
+
*
|
|
2503
|
+
* @param {string} key - The key to monitor.
|
|
2504
|
+
* @param {() => void} onHold - The callback function to execute on each `keydown` event for the specified key.
|
|
1985
2505
|
*/
|
|
1986
2506
|
function detectKeyHold(key, onHold) {
|
|
1987
2507
|
const keyDownHandler = (event) => {
|
|
@@ -1995,7 +2515,7 @@ function detectKeyHold(key, onHold) {
|
|
|
1995
2515
|
}
|
|
1996
2516
|
detectKeyHold.handlers = [];
|
|
1997
2517
|
/**
|
|
1998
|
-
*
|
|
2518
|
+
* Removes all key hold listeners that were added by `detectKeyHold`.
|
|
1999
2519
|
*/
|
|
2000
2520
|
function stopDetectingKeyHold() {
|
|
2001
2521
|
for (const handler of detectKeyHold.handlers) {
|
|
@@ -2004,8 +2524,9 @@ function stopDetectingKeyHold() {
|
|
|
2004
2524
|
detectKeyHold.handlers = [];
|
|
2005
2525
|
}
|
|
2006
2526
|
/**
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2527
|
+
* Creates and maintains a `Set` of currently pressed keys.
|
|
2528
|
+
*
|
|
2529
|
+
* @returns {Set<string>} A `Set` that dynamically updates with the keys being pressed.
|
|
2009
2530
|
*/
|
|
2010
2531
|
function createKeyMap() {
|
|
2011
2532
|
const pressedKeys = new Set();
|
|
@@ -2025,13 +2546,14 @@ function createKeyMap() {
|
|
|
2025
2546
|
return pressedKeys;
|
|
2026
2547
|
}
|
|
2027
2548
|
/**
|
|
2028
|
-
* Clears
|
|
2549
|
+
* Clears the `keydown` and `keyup` event listeners created by `createKeyMap`.
|
|
2029
2550
|
*/
|
|
2030
2551
|
createKeyMap.clearListeners = () => { };
|
|
2031
2552
|
/**
|
|
2032
|
-
* Sets up
|
|
2033
|
-
*
|
|
2034
|
-
* @param {
|
|
2553
|
+
* Sets up a keyboard shortcut that triggers a callback when a specific combination of keys is held down, regardless of order.
|
|
2554
|
+
*
|
|
2555
|
+
* @param {string[]} keys - An array of keys that constitute the shortcut.
|
|
2556
|
+
* @param {() => void} callback - The function to execute when the key combination is active.
|
|
2035
2557
|
*/
|
|
2036
2558
|
function customShortcut(keys, callback) {
|
|
2037
2559
|
const pressedKeys = new Set();
|
|
@@ -2051,7 +2573,7 @@ function customShortcut(keys, callback) {
|
|
|
2051
2573
|
}
|
|
2052
2574
|
customShortcut.handlers = [];
|
|
2053
2575
|
/**
|
|
2054
|
-
* Removes all
|
|
2576
|
+
* Removes all keyboard shortcut listeners that were added by `customShortcut`.
|
|
2055
2577
|
*/
|
|
2056
2578
|
function removeCustomShortcuts() {
|
|
2057
2579
|
for (const { keyDownHandler, keyUpHandler } of customShortcut.handlers) {
|
|
@@ -2061,11 +2583,12 @@ function removeCustomShortcuts() {
|
|
|
2061
2583
|
customShortcut.handlers = [];
|
|
2062
2584
|
}
|
|
2063
2585
|
/**
|
|
2064
|
-
*
|
|
2065
|
-
*
|
|
2066
|
-
* @param {
|
|
2067
|
-
* @param {boolean}
|
|
2068
|
-
* @param {boolean}
|
|
2586
|
+
* Programmatically dispatches a `keydown` event to simulate a key press.
|
|
2587
|
+
*
|
|
2588
|
+
* @param {string} key - The key to simulate (e.g., 'Enter', 'a').
|
|
2589
|
+
* @param {boolean} [ctrlKey=false] - Whether to simulate the Ctrl key being pressed.
|
|
2590
|
+
* @param {boolean} [shiftKey=false] - Whether to simulate the Shift key being pressed.
|
|
2591
|
+
* @param {boolean} [altKey=false] - Whether to simulate the Alt key being pressed.
|
|
2069
2592
|
*/
|
|
2070
2593
|
function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false) {
|
|
2071
2594
|
const event = new KeyboardEvent('keydown', {
|
|
@@ -2080,9 +2603,11 @@ function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false
|
|
|
2080
2603
|
}
|
|
2081
2604
|
|
|
2082
2605
|
/**
|
|
2083
|
-
* Parses a date string
|
|
2084
|
-
*
|
|
2085
|
-
*
|
|
2606
|
+
* Parses a date string in the format 'YYYY-MM-DD' and returns a `Date` object.
|
|
2607
|
+
* It includes validation to ensure the parsed date is a valid calendar date.
|
|
2608
|
+
*
|
|
2609
|
+
* @param {string} dateString - The date string to parse.
|
|
2610
|
+
* @returns {Date | null} A `Date` object if the string is a valid date, otherwise `null`.
|
|
2086
2611
|
*/
|
|
2087
2612
|
function parseDate(dateString) {
|
|
2088
2613
|
const parts = dateString.split('-');
|
|
@@ -2098,9 +2623,11 @@ function parseDate(dateString) {
|
|
|
2098
2623
|
return null;
|
|
2099
2624
|
}
|
|
2100
2625
|
/**
|
|
2101
|
-
* Formats a Date object into a string.
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2626
|
+
* Formats a `Date` object into a custom string format.
|
|
2627
|
+
* Supported format specifiers: YYYY, MM, DD, HH, mm, ss.
|
|
2628
|
+
*
|
|
2629
|
+
* @param {Date} date - The `Date` object to format.
|
|
2630
|
+
* @param {string} format - The desired string format (e.g., 'YYYY-MM-DD HH:mm:ss').
|
|
2104
2631
|
* @returns {string} The formatted date string.
|
|
2105
2632
|
*/
|
|
2106
2633
|
function formatDate(date, format) {
|
|
@@ -2115,9 +2642,10 @@ function formatDate(date, format) {
|
|
|
2115
2642
|
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (matched) => (map[matched] || matched).toString());
|
|
2116
2643
|
}
|
|
2117
2644
|
/**
|
|
2118
|
-
* Calculates the number of days between two dates.
|
|
2119
|
-
*
|
|
2120
|
-
* @param {Date}
|
|
2645
|
+
* Calculates the total number of full days between two dates.
|
|
2646
|
+
*
|
|
2647
|
+
* @param {Date} startDate - The starting date.
|
|
2648
|
+
* @param {Date} endDate - The ending date.
|
|
2121
2649
|
* @returns {number} The number of days between the two dates.
|
|
2122
2650
|
*/
|
|
2123
2651
|
function daysBetween(startDate, endDate) {
|
|
@@ -2125,10 +2653,11 @@ function daysBetween(startDate, endDate) {
|
|
|
2125
2653
|
return Math.round((endDate.getTime() - startDate.getTime()) / millisecondsPerDay);
|
|
2126
2654
|
}
|
|
2127
2655
|
/**
|
|
2128
|
-
* Adds a specified number of days to a date.
|
|
2129
|
-
*
|
|
2130
|
-
* @param {
|
|
2131
|
-
* @
|
|
2656
|
+
* Adds a specified number of days to a given date.
|
|
2657
|
+
*
|
|
2658
|
+
* @param {Date} date - The original date.
|
|
2659
|
+
* @param {number} days - The number of days to add (can be negative to subtract).
|
|
2660
|
+
* @returns {Date} A new `Date` object representing the resulting date.
|
|
2132
2661
|
*/
|
|
2133
2662
|
function addDays(date, days) {
|
|
2134
2663
|
const result = new Date(date);
|
|
@@ -2136,41 +2665,46 @@ function addDays(date, days) {
|
|
|
2136
2665
|
return result;
|
|
2137
2666
|
}
|
|
2138
2667
|
/**
|
|
2139
|
-
* Subtracts a specified number of days from a date.
|
|
2140
|
-
*
|
|
2141
|
-
* @param {
|
|
2142
|
-
* @
|
|
2668
|
+
* Subtracts a specified number of days from a given date.
|
|
2669
|
+
*
|
|
2670
|
+
* @param {Date} date - The original date.
|
|
2671
|
+
* @param {number} days - The number of days to subtract.
|
|
2672
|
+
* @returns {Date} A new `Date` object representing the resulting date.
|
|
2143
2673
|
*/
|
|
2144
2674
|
function subtractDays(date, days) {
|
|
2145
2675
|
return addDays(date, -days);
|
|
2146
2676
|
}
|
|
2147
2677
|
/**
|
|
2148
|
-
* Determines
|
|
2149
|
-
*
|
|
2150
|
-
* @
|
|
2678
|
+
* Determines whether a given year is a leap year according to the Gregorian calendar rules.
|
|
2679
|
+
*
|
|
2680
|
+
* @param {number} year - The year to check.
|
|
2681
|
+
* @returns {boolean} `true` if the year is a leap year, otherwise `false`.
|
|
2151
2682
|
*/
|
|
2152
2683
|
function isLeapYear(year) {
|
|
2153
2684
|
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
2154
2685
|
}
|
|
2155
2686
|
/**
|
|
2156
|
-
*
|
|
2157
|
-
*
|
|
2158
|
-
* @
|
|
2687
|
+
* Returns a new `Date` object set to the first day of the month for a given date.
|
|
2688
|
+
*
|
|
2689
|
+
* @param {Date} date - The date from which to determine the month and year.
|
|
2690
|
+
* @returns {Date} A `Date` object representing the start of the month.
|
|
2159
2691
|
*/
|
|
2160
2692
|
function getStartOfMonth(date) {
|
|
2161
2693
|
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
2162
2694
|
}
|
|
2163
2695
|
/**
|
|
2164
|
-
*
|
|
2165
|
-
*
|
|
2166
|
-
* @
|
|
2696
|
+
* Returns a new `Date` object set to the last day of the month for a given date.
|
|
2697
|
+
*
|
|
2698
|
+
* @param {Date} date - The date from which to determine the month and year.
|
|
2699
|
+
* @returns {Date} A `Date` object representing the end of the month.
|
|
2167
2700
|
*/
|
|
2168
2701
|
function getEndOfMonth(date) {
|
|
2169
2702
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
2170
2703
|
}
|
|
2171
2704
|
/**
|
|
2172
|
-
* Calculates age
|
|
2173
|
-
*
|
|
2705
|
+
* Calculates the current age in years based on a given birth date.
|
|
2706
|
+
*
|
|
2707
|
+
* @param {Date} birthDate - The date of birth.
|
|
2174
2708
|
* @returns {number} The calculated age.
|
|
2175
2709
|
*/
|
|
2176
2710
|
function calculateAge(birthDate) {
|
|
@@ -2183,8 +2717,9 @@ function calculateAge(birthDate) {
|
|
|
2183
2717
|
return age;
|
|
2184
2718
|
}
|
|
2185
2719
|
/**
|
|
2186
|
-
* Calculates the number of days until the next birthday.
|
|
2187
|
-
*
|
|
2720
|
+
* Calculates the number of days from the current date until the next birthday.
|
|
2721
|
+
*
|
|
2722
|
+
* @param {Date} birthDate - The date of birth.
|
|
2188
2723
|
* @returns {number} The number of days until the next birthday.
|
|
2189
2724
|
*/
|
|
2190
2725
|
function daysToNextBirthday(birthDate) {
|
|
@@ -2198,10 +2733,11 @@ function daysToNextBirthday(birthDate) {
|
|
|
2198
2733
|
return daysBetween(today, nextBirthday);
|
|
2199
2734
|
}
|
|
2200
2735
|
/**
|
|
2201
|
-
* Calculates the age
|
|
2202
|
-
*
|
|
2203
|
-
* @param {Date}
|
|
2204
|
-
* @
|
|
2736
|
+
* Calculates the age of a person on a specific date in the past or future.
|
|
2737
|
+
*
|
|
2738
|
+
* @param {Date} birthDate - The date of birth.
|
|
2739
|
+
* @param {Date} atDate - The target date for which to calculate the age.
|
|
2740
|
+
* @returns {number} The age on the specified date.
|
|
2205
2741
|
*/
|
|
2206
2742
|
function ageAtDate(birthDate, atDate) {
|
|
2207
2743
|
let age = atDate.getFullYear() - birthDate.getFullYear();
|
|
@@ -2213,11 +2749,12 @@ function ageAtDate(birthDate, atDate) {
|
|
|
2213
2749
|
}
|
|
2214
2750
|
|
|
2215
2751
|
/**
|
|
2216
|
-
* Creates a debounced asynchronous validator function.
|
|
2752
|
+
* Creates a debounced version of an asynchronous validator function. This is useful for scenarios
|
|
2753
|
+
* like form input validation where you want to delay validation until the user has stopped typing.
|
|
2217
2754
|
*
|
|
2218
|
-
* @param validator - The
|
|
2219
|
-
* @param delay - The debounce delay in milliseconds.
|
|
2220
|
-
* @returns A
|
|
2755
|
+
* @param validator - The asynchronous validator function to be debounced. It receives the value to validate and a `debounce` function.
|
|
2756
|
+
* @param {number} delay - The debounce delay in milliseconds.
|
|
2757
|
+
* @returns A new function that takes a value and returns a promise that resolves or rejects based on the debounced validation.
|
|
2221
2758
|
*/
|
|
2222
2759
|
function debounceAsyncValidator(validator, delay) {
|
|
2223
2760
|
let currentPromiseReject = null;
|
|
@@ -2248,10 +2785,13 @@ function debounceAsyncValidator(validator, delay) {
|
|
|
2248
2785
|
};
|
|
2249
2786
|
}
|
|
2250
2787
|
/**
|
|
2251
|
-
* Creates a debounced version of an asynchronous function.
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
2254
|
-
* @
|
|
2788
|
+
* Creates a debounced version of an asynchronous function. The debounced function will only
|
|
2789
|
+
* resolve the promise of the last invocation within the `wait` period.
|
|
2790
|
+
*
|
|
2791
|
+
* @template T - The type of the asynchronous function.
|
|
2792
|
+
* @param {T} func - The asynchronous function to debounce.
|
|
2793
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2794
|
+
* @returns A new debounced asynchronous function.
|
|
2255
2795
|
*/
|
|
2256
2796
|
function debounceAsync(func, wait) {
|
|
2257
2797
|
let timeoutReject = null;
|
|
@@ -2270,10 +2810,13 @@ function debounceAsync(func, wait) {
|
|
|
2270
2810
|
};
|
|
2271
2811
|
}
|
|
2272
2812
|
/**
|
|
2273
|
-
* Creates a debounced asynchronous function that executes immediately on the first call
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
* @
|
|
2813
|
+
* Creates a debounced version of an asynchronous function that executes immediately on the first call
|
|
2814
|
+
* and then waits for the specified delay before allowing the next execution.
|
|
2815
|
+
*
|
|
2816
|
+
* @template T - The type of the asynchronous function.
|
|
2817
|
+
* @param {T} func - The asynchronous function to debounce.
|
|
2818
|
+
* @param {number} wait - The cooldown period in milliseconds after an immediate execution.
|
|
2819
|
+
* @returns A new debounced asynchronous function that executes on the leading edge.
|
|
2277
2820
|
*/
|
|
2278
2821
|
function debounceAsyncWithImmediate(func, wait) {
|
|
2279
2822
|
let timeoutReject = null;
|
|
@@ -2305,10 +2848,13 @@ function debounceAsyncWithImmediate(func, wait) {
|
|
|
2305
2848
|
};
|
|
2306
2849
|
}
|
|
2307
2850
|
/**
|
|
2308
|
-
* Creates a debounced
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2311
|
-
* @
|
|
2851
|
+
* Creates a debounced function that invokes `func` on the leading edge of the `wait` timeout.
|
|
2852
|
+
* Subsequent calls within the `wait` period are ignored.
|
|
2853
|
+
*
|
|
2854
|
+
* @template T - The type of the function.
|
|
2855
|
+
* @param {T} func - The function to debounce.
|
|
2856
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2857
|
+
* @returns A new debounced function.
|
|
2312
2858
|
*/
|
|
2313
2859
|
function debounceLeading(func, wait) {
|
|
2314
2860
|
let timeoutReject = null;
|
|
@@ -2329,10 +2875,13 @@ function debounceLeading(func, wait) {
|
|
|
2329
2875
|
};
|
|
2330
2876
|
}
|
|
2331
2877
|
/**
|
|
2332
|
-
* Creates a debounced
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
* @
|
|
2878
|
+
* Creates a debounced function that invokes `func` on the trailing edge of the `wait` timeout.
|
|
2879
|
+
* The function is called only after `wait` milliseconds of inactivity.
|
|
2880
|
+
*
|
|
2881
|
+
* @template T - The type of the function.
|
|
2882
|
+
* @param {T} func - The function to debounce.
|
|
2883
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2884
|
+
* @returns A new debounced function.
|
|
2336
2885
|
*/
|
|
2337
2886
|
function debounceTrailing(func, wait) {
|
|
2338
2887
|
let timeoutReject = null;
|
|
@@ -2350,10 +2899,13 @@ function debounceTrailing(func, wait) {
|
|
|
2350
2899
|
};
|
|
2351
2900
|
}
|
|
2352
2901
|
/**
|
|
2353
|
-
* Creates a debounced
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
* @
|
|
2902
|
+
* Creates a debounced function that invokes `func` on both the leading and trailing edges of the `wait` timeout.
|
|
2903
|
+
* This is useful for UIs where an action should happen immediately on the first event, but also after a pause in events.
|
|
2904
|
+
*
|
|
2905
|
+
* @template T - The type of the function.
|
|
2906
|
+
* @param {T} func - The function to debounce.
|
|
2907
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2908
|
+
* @returns A new debounced function.
|
|
2357
2909
|
*/
|
|
2358
2910
|
function debounceLeadingTrailing(func, wait) {
|
|
2359
2911
|
let timeoutReject = null;
|
|
@@ -2379,10 +2931,13 @@ function debounceLeadingTrailing(func, wait) {
|
|
|
2379
2931
|
};
|
|
2380
2932
|
}
|
|
2381
2933
|
/**
|
|
2382
|
-
* Creates a debounced
|
|
2383
|
-
*
|
|
2384
|
-
*
|
|
2385
|
-
* @
|
|
2934
|
+
* Creates a standard debounced function that delays invoking `func` until after `wait` milliseconds
|
|
2935
|
+
* have elapsed since the last time the debounced function was invoked. (This is an alias for `debounceTrailing`).
|
|
2936
|
+
*
|
|
2937
|
+
* @template T - The type of the function.
|
|
2938
|
+
* @param {T} func - The function to debounce.
|
|
2939
|
+
* @param {number} wait - The debounce delay in milliseconds.
|
|
2940
|
+
* @returns A new debounced function.
|
|
2386
2941
|
*/
|
|
2387
2942
|
function debounce(func, wait) {
|
|
2388
2943
|
let timeoutReject = null;
|
|
@@ -2399,10 +2954,13 @@ function debounce(func, wait) {
|
|
|
2399
2954
|
};
|
|
2400
2955
|
}
|
|
2401
2956
|
/**
|
|
2402
|
-
* Creates a throttled
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
* @
|
|
2957
|
+
* Creates a throttled function that only invokes `func` at most once per every `limit` milliseconds.
|
|
2958
|
+
* This is useful for rate-limiting events that fire frequently, such as scrolling or resizing.
|
|
2959
|
+
*
|
|
2960
|
+
* @template T - The type of the function.
|
|
2961
|
+
* @param {T} func - The function to throttle.
|
|
2962
|
+
* @param {number} limit - The throttle duration in milliseconds.
|
|
2963
|
+
* @returns A new throttled function.
|
|
2406
2964
|
*/
|
|
2407
2965
|
function throttle(func, limit) {
|
|
2408
2966
|
let lastCall = 0;
|
|
@@ -2416,9 +2974,11 @@ function throttle(func, limit) {
|
|
|
2416
2974
|
}
|
|
2417
2975
|
|
|
2418
2976
|
/**
|
|
2419
|
-
* Converts a FormData object
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2977
|
+
* Converts a `FormData` object into a regular JavaScript object. It correctly handles
|
|
2978
|
+
* multiple values for the same key by creating an array for that key.
|
|
2979
|
+
*
|
|
2980
|
+
* @param {FormData} formData - The `FormData` object to convert.
|
|
2981
|
+
* @returns {Record<string, any>} A plain JavaScript object representation of the FormData.
|
|
2422
2982
|
*/
|
|
2423
2983
|
function formDataToObject(formData) {
|
|
2424
2984
|
const obj = {};
|
|
@@ -2437,9 +2997,10 @@ function formDataToObject(formData) {
|
|
|
2437
2997
|
return obj;
|
|
2438
2998
|
}
|
|
2439
2999
|
/**
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
2442
|
-
* @
|
|
3000
|
+
* Asynchronously reads the content of a `File` object as a text string.
|
|
3001
|
+
*
|
|
3002
|
+
* @param {File} file - The `File` object to read.
|
|
3003
|
+
* @returns {Promise<string>} A promise that resolves with the text content of the file.
|
|
2443
3004
|
*/
|
|
2444
3005
|
function readFileAsText(file) {
|
|
2445
3006
|
return new Promise((resolve, reject) => {
|
|
@@ -2450,9 +3011,10 @@ function readFileAsText(file) {
|
|
|
2450
3011
|
});
|
|
2451
3012
|
}
|
|
2452
3013
|
/**
|
|
2453
|
-
*
|
|
2454
|
-
*
|
|
2455
|
-
* @
|
|
3014
|
+
* Asynchronously reads the content of a `File` object as a Base64-encoded Data URL.
|
|
3015
|
+
*
|
|
3016
|
+
* @param {File} file - The `File` object to read.
|
|
3017
|
+
* @returns {Promise<string>} A promise that resolves with the Data URL representing the file's content.
|
|
2456
3018
|
*/
|
|
2457
3019
|
function readFileAsDataURL(file) {
|
|
2458
3020
|
return new Promise((resolve, reject) => {
|
|
@@ -2463,27 +3025,30 @@ function readFileAsDataURL(file) {
|
|
|
2463
3025
|
});
|
|
2464
3026
|
}
|
|
2465
3027
|
/**
|
|
2466
|
-
* Creates a Blob from a string.
|
|
2467
|
-
*
|
|
2468
|
-
* @param {string}
|
|
2469
|
-
* @
|
|
3028
|
+
* Creates a `Blob` object from a string.
|
|
3029
|
+
*
|
|
3030
|
+
* @param {string} content - The string content to be put into the Blob.
|
|
3031
|
+
* @param {string} [type='text/plain'] - The MIME type of the Blob.
|
|
3032
|
+
* @returns {Blob} A new `Blob` object.
|
|
2470
3033
|
*/
|
|
2471
3034
|
function stringToBlob(content, type = 'text/plain') {
|
|
2472
3035
|
return new Blob([content], { type });
|
|
2473
3036
|
}
|
|
2474
3037
|
/**
|
|
2475
|
-
* Creates a Blob from an ArrayBuffer
|
|
2476
|
-
*
|
|
2477
|
-
* @param {
|
|
2478
|
-
* @
|
|
3038
|
+
* Creates a `Blob` object from an `ArrayBuffer`.
|
|
3039
|
+
*
|
|
3040
|
+
* @param {ArrayBuffer} buffer - The `ArrayBuffer` to be put into the Blob.
|
|
3041
|
+
* @param {string} [type='application/octet-stream'] - The MIME type of the Blob.
|
|
3042
|
+
* @returns {Blob} A new `Blob` object.
|
|
2479
3043
|
*/
|
|
2480
3044
|
function bufferToBlob(buffer, type = 'application/octet-stream') {
|
|
2481
3045
|
return new Blob([buffer], { type });
|
|
2482
3046
|
}
|
|
2483
3047
|
/**
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2486
|
-
* @param {
|
|
3048
|
+
* Triggers a browser download for a file created from a `Blob` object.
|
|
3049
|
+
*
|
|
3050
|
+
* @param {Blob} blob - The `Blob` containing the file data.
|
|
3051
|
+
* @param {string} fileName - The desired name for the downloaded file.
|
|
2487
3052
|
*/
|
|
2488
3053
|
function downloadBlob(blob, fileName) {
|
|
2489
3054
|
const link = document.createElement('a');
|
|
@@ -2498,11 +3063,12 @@ function downloadBlob(blob, fileName) {
|
|
|
2498
3063
|
URL.revokeObjectURL(url);
|
|
2499
3064
|
}
|
|
2500
3065
|
/**
|
|
2501
|
-
*
|
|
2502
|
-
*
|
|
2503
|
-
* @param {
|
|
2504
|
-
* @param {string}
|
|
2505
|
-
* @
|
|
3066
|
+
* Appends a `Blob` to a new `FormData` object.
|
|
3067
|
+
*
|
|
3068
|
+
* @param {Blob} blob - The `Blob` to append.
|
|
3069
|
+
* @param {string} name - The name of the field to append the blob as.
|
|
3070
|
+
* @param {string} [fileName='file'] - The filename to associate with the blob in the `FormData`.
|
|
3071
|
+
* @returns {FormData} A new `FormData` object containing the blob.
|
|
2506
3072
|
*/
|
|
2507
3073
|
function blobToFormData(blob, name, fileName = 'file') {
|
|
2508
3074
|
const formData = new FormData();
|
|
@@ -2511,9 +3077,10 @@ function blobToFormData(blob, name, fileName = 'file') {
|
|
|
2511
3077
|
}
|
|
2512
3078
|
|
|
2513
3079
|
/**
|
|
2514
|
-
* Capitalizes the first
|
|
2515
|
-
*
|
|
2516
|
-
* @
|
|
3080
|
+
* Capitalizes the first letter of a string.
|
|
3081
|
+
*
|
|
3082
|
+
* @param {string} s - The input string.
|
|
3083
|
+
* @returns {string} The string with the first letter capitalized.
|
|
2517
3084
|
*/
|
|
2518
3085
|
function upperFirst(s) {
|
|
2519
3086
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -2521,9 +3088,10 @@ function upperFirst(s) {
|
|
|
2521
3088
|
// Example usage:
|
|
2522
3089
|
// console.log(upperFirst('hello')); // 'Hello'
|
|
2523
3090
|
/**
|
|
2524
|
-
*
|
|
2525
|
-
*
|
|
2526
|
-
* @
|
|
3091
|
+
* Converts the first letter of a string to lowercase.
|
|
3092
|
+
*
|
|
3093
|
+
* @param {string} s - The input string.
|
|
3094
|
+
* @returns {string} The string with the first letter in lowercase.
|
|
2527
3095
|
*/
|
|
2528
3096
|
function lowerFirst(s) {
|
|
2529
3097
|
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
@@ -2531,9 +3099,11 @@ function lowerFirst(s) {
|
|
|
2531
3099
|
// Example usage:
|
|
2532
3100
|
// console.log(lowerFirst('Hello')); // 'hello'
|
|
2533
3101
|
/**
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2536
|
-
*
|
|
3102
|
+
* Normalizes a string by converting it to lowercase, trimming whitespace, replacing spaces with hyphens,
|
|
3103
|
+
* and removing diacritical marks (accents).
|
|
3104
|
+
*
|
|
3105
|
+
* @param {string} input - The string to normalize.
|
|
3106
|
+
* @returns {string} The URL-friendly, normalized string.
|
|
2537
3107
|
*/
|
|
2538
3108
|
function removeAccent(input) {
|
|
2539
3109
|
return input
|
|
@@ -2547,8 +3117,9 @@ function removeAccent(input) {
|
|
|
2547
3117
|
// Example usage:
|
|
2548
3118
|
// console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
|
|
2549
3119
|
/**
|
|
2550
|
-
* Reverses a string.
|
|
2551
|
-
*
|
|
3120
|
+
* Reverses the characters of a string.
|
|
3121
|
+
*
|
|
3122
|
+
* @param {string} str - The input string.
|
|
2552
3123
|
* @returns {string} The reversed string.
|
|
2553
3124
|
*/
|
|
2554
3125
|
function reverseString(str) {
|
|
@@ -2557,9 +3128,10 @@ function reverseString(str) {
|
|
|
2557
3128
|
// Example usage:
|
|
2558
3129
|
// console.log(reverseString('hello')); // 'olleh'
|
|
2559
3130
|
/**
|
|
2560
|
-
* Counts the number of words in a string.
|
|
2561
|
-
*
|
|
2562
|
-
* @
|
|
3131
|
+
* Counts the number of words in a string, based on whitespace separation.
|
|
3132
|
+
*
|
|
3133
|
+
* @param {string} str - The input string.
|
|
3134
|
+
* @returns {number} The number of words in the string.
|
|
2563
3135
|
*/
|
|
2564
3136
|
function countWords(str) {
|
|
2565
3137
|
return str.trim().split(/\s+/).length;
|
|
@@ -2567,9 +3139,10 @@ function countWords(str) {
|
|
|
2567
3139
|
// Example usage:
|
|
2568
3140
|
// console.log(countWords('Hello world!')); // 2
|
|
2569
3141
|
/**
|
|
2570
|
-
* Truncates a string to
|
|
2571
|
-
*
|
|
2572
|
-
* @param {
|
|
3142
|
+
* Truncates a string to a specified maximum length, appending '...' if the string is cut.
|
|
3143
|
+
*
|
|
3144
|
+
* @param {string} str - The input string.
|
|
3145
|
+
* @param {number} maxLength - The maximum desired length of the string.
|
|
2573
3146
|
* @returns {string} The truncated string.
|
|
2574
3147
|
*/
|
|
2575
3148
|
function truncateString(str, maxLength) {
|
|
@@ -2578,9 +3151,10 @@ function truncateString(str, maxLength) {
|
|
|
2578
3151
|
// Example usage:
|
|
2579
3152
|
// console.log(truncateString('This is a long string', 10)); // 'This is a...'
|
|
2580
3153
|
/**
|
|
2581
|
-
* Converts a string
|
|
2582
|
-
*
|
|
2583
|
-
* @
|
|
3154
|
+
* Converts a string from various cases (e.g., snake_case, kebab-case, space separated) into camelCase.
|
|
3155
|
+
*
|
|
3156
|
+
* @param {string} str - The input string.
|
|
3157
|
+
* @returns {string} The camelCase version of the string.
|
|
2584
3158
|
*/
|
|
2585
3159
|
function toCamelCase(str) {
|
|
2586
3160
|
return str
|
|
@@ -2590,9 +3164,10 @@ function toCamelCase(str) {
|
|
|
2590
3164
|
// Example usage:
|
|
2591
3165
|
// console.log(toCamelCase('hello world example')); // 'helloWorldExample'
|
|
2592
3166
|
/**
|
|
2593
|
-
* Converts a string
|
|
2594
|
-
*
|
|
2595
|
-
* @
|
|
3167
|
+
* Converts a string from various cases (e.g., camelCase, PascalCase, space separated) into kebab-case.
|
|
3168
|
+
*
|
|
3169
|
+
* @param {string} str - The input string.
|
|
3170
|
+
* @returns {string} The kebab-case version of the string.
|
|
2596
3171
|
*/
|
|
2597
3172
|
function toKebabCase(str) {
|
|
2598
3173
|
return str
|
|
@@ -2603,11 +3178,12 @@ function toKebabCase(str) {
|
|
|
2603
3178
|
// Example usage:
|
|
2604
3179
|
// console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
|
|
2605
3180
|
/**
|
|
2606
|
-
* Replaces all
|
|
2607
|
-
*
|
|
2608
|
-
* @param {string}
|
|
2609
|
-
* @param {string}
|
|
2610
|
-
* @
|
|
3181
|
+
* Replaces all occurrences of a substring with a new substring.
|
|
3182
|
+
*
|
|
3183
|
+
* @param {string} str - The original string.
|
|
3184
|
+
* @param {string} find - The substring to find and replace.
|
|
3185
|
+
* @param {string} replace - The substring to replace with.
|
|
3186
|
+
* @returns {string} A new string with all replacements made.
|
|
2611
3187
|
*/
|
|
2612
3188
|
function replaceAll(str, find, replace) {
|
|
2613
3189
|
return str.split(find).join(replace);
|
|
@@ -2615,9 +3191,10 @@ function replaceAll(str, find, replace) {
|
|
|
2615
3191
|
// Example usage:
|
|
2616
3192
|
// console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
|
|
2617
3193
|
/**
|
|
2618
|
-
* Generates a random string of a
|
|
2619
|
-
*
|
|
2620
|
-
* @
|
|
3194
|
+
* Generates a random alphanumeric string of a specified length.
|
|
3195
|
+
*
|
|
3196
|
+
* @param {number} length - The desired length of the random string.
|
|
3197
|
+
* @returns {string} The generated random string.
|
|
2621
3198
|
*/
|
|
2622
3199
|
function generateRandomString(length) {
|
|
2623
3200
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
@@ -2627,9 +3204,10 @@ function generateRandomString(length) {
|
|
|
2627
3204
|
// console.log(generateRandomString(10)); // 'A1b2C3d4E5'
|
|
2628
3205
|
|
|
2629
3206
|
/**
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
* @
|
|
3207
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not a letter or a specific special character.
|
|
3208
|
+
*
|
|
3209
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3210
|
+
* @returns {boolean} `true` if the key is valid, `false` otherwise.
|
|
2633
3211
|
*/
|
|
2634
3212
|
function validateLetters(e) {
|
|
2635
3213
|
const key = e.keyCode;
|
|
@@ -2660,9 +3238,10 @@ function validateLetters(e) {
|
|
|
2660
3238
|
// Example usage:
|
|
2661
3239
|
// document.addEventListener('keydown', validateLetters);
|
|
2662
3240
|
/**
|
|
2663
|
-
*
|
|
2664
|
-
*
|
|
2665
|
-
* @
|
|
3241
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not an alphanumeric character.
|
|
3242
|
+
*
|
|
3243
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3244
|
+
* @returns {boolean} `true` if the key is valid, `false` otherwise.
|
|
2666
3245
|
*/
|
|
2667
3246
|
function validateAlphanumeric(e) {
|
|
2668
3247
|
const key = e.keyCode;
|
|
@@ -2683,9 +3262,10 @@ function validateAlphanumeric(e) {
|
|
|
2683
3262
|
// Example usage:
|
|
2684
3263
|
// document.addEventListener('keydown', validateAlphanumeric);
|
|
2685
3264
|
/**
|
|
2686
|
-
*
|
|
2687
|
-
*
|
|
2688
|
-
* @
|
|
3265
|
+
* An event handler that prevents a `KeyboardEvent`'s default action if the key pressed is not a number.
|
|
3266
|
+
*
|
|
3267
|
+
* @param {KeyboardEvent} e - The `KeyboardEvent` object.
|
|
3268
|
+
* @returns {boolean} `true` if the key is a number, otherwise `false`.
|
|
2689
3269
|
*/
|
|
2690
3270
|
function validateNumbers(e) {
|
|
2691
3271
|
const key = e.keyCode;
|
|
@@ -2698,9 +3278,10 @@ function validateNumbers(e) {
|
|
|
2698
3278
|
// Example usage:
|
|
2699
3279
|
// document.addEventListener('keydown', validateNumbers);
|
|
2700
3280
|
/**
|
|
2701
|
-
* Validates
|
|
2702
|
-
*
|
|
2703
|
-
* @
|
|
3281
|
+
* Validates a phone number against a regex for 10-digit numbers.
|
|
3282
|
+
*
|
|
3283
|
+
* @param {string} phoneNumber - The phone number to validate.
|
|
3284
|
+
* @returns {boolean} `true` if the phone number is valid, otherwise `false`.
|
|
2704
3285
|
*/
|
|
2705
3286
|
function isValidPhoneNumber(phoneNumber) {
|
|
2706
3287
|
const phonePattern = /^[0-9]{10}$/; // Example pattern for 10-digit phone numbers
|
|
@@ -2710,9 +3291,10 @@ function isValidPhoneNumber(phoneNumber) {
|
|
|
2710
3291
|
// console.log(isValidPhoneNumber('1234567890')); // true
|
|
2711
3292
|
// console.log(isValidPhoneNumber('123-456-7890')); // false
|
|
2712
3293
|
/**
|
|
2713
|
-
* Validates
|
|
2714
|
-
*
|
|
2715
|
-
* @
|
|
3294
|
+
* Validates an email address against a standard regex pattern.
|
|
3295
|
+
*
|
|
3296
|
+
* @param {string} email - The email address to validate.
|
|
3297
|
+
* @returns {boolean} `true` if the email is valid, otherwise `false`.
|
|
2716
3298
|
*/
|
|
2717
3299
|
function isValidEmail(email) {
|
|
2718
3300
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -2722,9 +3304,10 @@ function isValidEmail(email) {
|
|
|
2722
3304
|
// console.log(isValidEmail('example@domain.com')); // true
|
|
2723
3305
|
// console.log(isValidEmail('invalid-email')); // false
|
|
2724
3306
|
/**
|
|
2725
|
-
* Validates
|
|
2726
|
-
*
|
|
2727
|
-
* @
|
|
3307
|
+
* Validates a string to see if it is a well-formed URL.
|
|
3308
|
+
*
|
|
3309
|
+
* @param {string} url - The URL string to validate.
|
|
3310
|
+
* @returns {boolean} `true` if the URL is valid, otherwise `false`.
|
|
2728
3311
|
*/
|
|
2729
3312
|
function isValidURL(url) {
|
|
2730
3313
|
try {
|
|
@@ -2739,9 +3322,10 @@ function isValidURL(url) {
|
|
|
2739
3322
|
// console.log(isValidURL('https://www.example.com')); // true
|
|
2740
3323
|
// console.log(isValidURL('invalid-url')); // false
|
|
2741
3324
|
/**
|
|
2742
|
-
* Validates
|
|
2743
|
-
*
|
|
2744
|
-
* @
|
|
3325
|
+
* Validates a string to ensure it is a valid date in 'YYYY-MM-DD' format.
|
|
3326
|
+
*
|
|
3327
|
+
* @param {string} date - The date string to validate.
|
|
3328
|
+
* @returns {boolean} `true` if the date is valid, otherwise `false`.
|
|
2745
3329
|
*/
|
|
2746
3330
|
function isValidDate(date) {
|
|
2747
3331
|
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -2755,9 +3339,10 @@ function isValidDate(date) {
|
|
|
2755
3339
|
// console.log(isValidDate('2024-08-31')); // true
|
|
2756
3340
|
// console.log(isValidDate('2024-02-30')); // false
|
|
2757
3341
|
/**
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2760
|
-
* @
|
|
3342
|
+
* Checks if a password meets a set of strength requirements (minimum length, uppercase, lowercase, numbers, special characters).
|
|
3343
|
+
*
|
|
3344
|
+
* @param {string} password - The password to validate.
|
|
3345
|
+
* @returns {boolean} `true` if the password is strong, otherwise `false`.
|
|
2761
3346
|
*/
|
|
2762
3347
|
function isStrongPassword(password) {
|
|
2763
3348
|
const minLength = 8;
|
|
@@ -2771,9 +3356,10 @@ function isStrongPassword(password) {
|
|
|
2771
3356
|
// console.log(isStrongPassword('Strong1@password')); // true
|
|
2772
3357
|
// console.log(isStrongPassword('weakpass')); // false
|
|
2773
3358
|
/**
|
|
2774
|
-
* Validates a credit card number using the Luhn algorithm.
|
|
2775
|
-
*
|
|
2776
|
-
* @
|
|
3359
|
+
* Validates a credit card number using the Luhn algorithm (mod-10 check).
|
|
3360
|
+
*
|
|
3361
|
+
* @param {string} cardNumber - The credit card number to validate.
|
|
3362
|
+
* @returns {boolean} `true` if the credit card number is valid, otherwise `false`.
|
|
2777
3363
|
*/
|
|
2778
3364
|
function isValidCreditCard(cardNumber) {
|
|
2779
3365
|
const sanitized = cardNumber.replace(/\D/g, '');
|
|
@@ -2795,9 +3381,10 @@ function isValidCreditCard(cardNumber) {
|
|
|
2795
3381
|
// console.log(isValidCreditCard('4111111111111111')); // true
|
|
2796
3382
|
// console.log(isValidCreditCard('1234567812345670')); // false
|
|
2797
3383
|
/**
|
|
2798
|
-
* Validates
|
|
2799
|
-
*
|
|
2800
|
-
* @
|
|
3384
|
+
* Validates a string to check if it's a valid 3- or 6-digit hexadecimal color code.
|
|
3385
|
+
*
|
|
3386
|
+
* @param {string} color - The hex color string to validate.
|
|
3387
|
+
* @returns {boolean} `true` if the color code is valid, otherwise `false`.
|
|
2801
3388
|
*/
|
|
2802
3389
|
function isValidHexColor(color) {
|
|
2803
3390
|
const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
|
|
@@ -2808,9 +3395,10 @@ function isValidHexColor(color) {
|
|
|
2808
3395
|
// console.log(isValidHexColor('#FFF')); // true
|
|
2809
3396
|
// console.log(isValidHexColor('#12345G')); // false
|
|
2810
3397
|
/**
|
|
2811
|
-
* Validates
|
|
2812
|
-
*
|
|
2813
|
-
* @
|
|
3398
|
+
* Validates a string to ensure it represents a valid time in 24-hour HH:MM format.
|
|
3399
|
+
*
|
|
3400
|
+
* @param {string} time - The time string to validate.
|
|
3401
|
+
* @returns {boolean} `true` if the time is valid, otherwise `false`.
|
|
2814
3402
|
*/
|
|
2815
3403
|
function isValidTime(time) {
|
|
2816
3404
|
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
@@ -2820,9 +3408,10 @@ function isValidTime(time) {
|
|
|
2820
3408
|
// console.log(isValidTime('14:30')); // true
|
|
2821
3409
|
// console.log(isValidTime('25:00')); // false
|
|
2822
3410
|
/**
|
|
2823
|
-
* Validates
|
|
2824
|
-
*
|
|
2825
|
-
* @
|
|
3411
|
+
* Validates a string to check if it is a valid IPv4 address.
|
|
3412
|
+
*
|
|
3413
|
+
* @param {string} ip - The IP address string to validate.
|
|
3414
|
+
* @returns {boolean} `true` if the IP address is valid, otherwise `false`.
|
|
2826
3415
|
*/
|
|
2827
3416
|
function isValidIP(ip) {
|
|
2828
3417
|
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 +3421,10 @@ function isValidIP(ip) {
|
|
|
2832
3421
|
// console.log(isValidIP('192.168.1.1')); // true
|
|
2833
3422
|
// console.log(isValidIP('999.999.999.999')); // false
|
|
2834
3423
|
/**
|
|
2835
|
-
* Validates
|
|
2836
|
-
*
|
|
2837
|
-
* @
|
|
3424
|
+
* Validates a string to check if it matches the format of a U.S. Social Security Number (SSN).
|
|
3425
|
+
*
|
|
3426
|
+
* @param {string} ssn - The SSN string to validate.
|
|
3427
|
+
* @returns {boolean} `true` if the SSN format is valid, otherwise `false`.
|
|
2838
3428
|
*/
|
|
2839
3429
|
function isValidSSN(ssn) {
|
|
2840
3430
|
const ssnPattern = /^\d{3}-\d{2}-\d{4}$/;
|
|
@@ -2844,9 +3434,10 @@ function isValidSSN(ssn) {
|
|
|
2844
3434
|
// console.log(isValidSSN('123-45-6789')); // true
|
|
2845
3435
|
// console.log(isValidSSN('123-45-678')); // false
|
|
2846
3436
|
/**
|
|
2847
|
-
* Validates
|
|
2848
|
-
*
|
|
2849
|
-
* @
|
|
3437
|
+
* Validates a string to check if it is a valid 5-digit or 9-digit (ZIP+4) U.S. ZIP code.
|
|
3438
|
+
*
|
|
3439
|
+
* @param {string} zip - The ZIP code string to validate.
|
|
3440
|
+
* @returns {boolean} `true` if the ZIP code is valid, otherwise `false`.
|
|
2850
3441
|
*/
|
|
2851
3442
|
function isValidZIP(zip) {
|
|
2852
3443
|
const zipPattern = /^\d{5}(-\d{4})?$/;
|
|
@@ -2857,9 +3448,10 @@ function isValidZIP(zip) {
|
|
|
2857
3448
|
// console.log(isValidZIP('12345-6789')); // true
|
|
2858
3449
|
// console.log(isValidZIP('1234')); // false
|
|
2859
3450
|
/**
|
|
2860
|
-
* Validates
|
|
2861
|
-
*
|
|
2862
|
-
* @
|
|
3451
|
+
* Validates a credit card expiry date string (MM/YY format) to ensure it is a valid, non-expired date.
|
|
3452
|
+
*
|
|
3453
|
+
* @param {string} expiryDate - The expiry date to validate.
|
|
3454
|
+
* @returns {boolean} `true` if the expiry date is valid and not in the past, otherwise `false`.
|
|
2863
3455
|
*/
|
|
2864
3456
|
function isValidExpiryDate(expiryDate) {
|
|
2865
3457
|
const expiryPattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
|
|
@@ -2874,9 +3466,10 @@ function isValidExpiryDate(expiryDate) {
|
|
|
2874
3466
|
// console.log(isValidExpiryDate('08/24')); // true
|
|
2875
3467
|
// console.log(isValidExpiryDate('12/22')); // false
|
|
2876
3468
|
/**
|
|
2877
|
-
* Validates
|
|
2878
|
-
*
|
|
2879
|
-
* @
|
|
3469
|
+
* Validates a string to check if it's a valid 8-digit hexadecimal color code (with alpha channel).
|
|
3470
|
+
*
|
|
3471
|
+
* @param {string} color - The hex color string to validate.
|
|
3472
|
+
* @returns {boolean} `true` if the color code is valid, otherwise `false`.
|
|
2880
3473
|
*/
|
|
2881
3474
|
function isValidHexColorAlpha(color) {
|
|
2882
3475
|
const hexPattern = /^#([0-9A-Fa-f]{8})$/;
|
|
@@ -2886,9 +3479,10 @@ function isValidHexColorAlpha(color) {
|
|
|
2886
3479
|
// console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
|
|
2887
3480
|
// console.log(isValidHexColorAlpha('#FFFFFF')); // false
|
|
2888
3481
|
/**
|
|
2889
|
-
* Validates
|
|
2890
|
-
*
|
|
2891
|
-
* @
|
|
3482
|
+
* Validates a username to ensure it contains only alphanumeric characters and underscores, with a length between 3 and 16 characters.
|
|
3483
|
+
*
|
|
3484
|
+
* @param {string} username - The username to validate.
|
|
3485
|
+
* @returns {boolean} `true` if the username is valid, otherwise `false`.
|
|
2892
3486
|
*/
|
|
2893
3487
|
function isValidUsername(username) {
|
|
2894
3488
|
const usernamePattern = /^[a-zA-Z0-9_]{3,16}$/; // 3 to 16 characters, letters, numbers, and underscores only
|
|
@@ -2898,9 +3492,10 @@ function isValidUsername(username) {
|
|
|
2898
3492
|
// console.log(isValidUsername('user_name123')); // true
|
|
2899
3493
|
// console.log(isValidUsername('us')); // false
|
|
2900
3494
|
/**
|
|
2901
|
-
* Validates
|
|
2902
|
-
*
|
|
2903
|
-
* @
|
|
3495
|
+
* Validates a string to ensure it represents a plausible human age (0-120).
|
|
3496
|
+
*
|
|
3497
|
+
* @param {string} age - The age string to validate.
|
|
3498
|
+
* @returns {boolean} `true` if the age is valid, otherwise `false`.
|
|
2904
3499
|
*/
|
|
2905
3500
|
function isValidAge(age) {
|
|
2906
3501
|
const ageNumber = parseInt(age, 10);
|
|
@@ -2910,9 +3505,10 @@ function isValidAge(age) {
|
|
|
2910
3505
|
// console.log(isValidAge('25')); // true
|
|
2911
3506
|
// console.log(isValidAge('121')); // false
|
|
2912
3507
|
/**
|
|
2913
|
-
* Validates
|
|
2914
|
-
*
|
|
2915
|
-
* @
|
|
3508
|
+
* Validates a string to check if it contains only valid hexadecimal characters.
|
|
3509
|
+
*
|
|
3510
|
+
* @param {string} hex - The string to validate.
|
|
3511
|
+
* @returns {boolean} `true` if the string is a valid hexadecimal number, otherwise `false`.
|
|
2916
3512
|
*/
|
|
2917
3513
|
function isValidHexNumber(hex) {
|
|
2918
3514
|
const hexPattern = /^[0-9A-Fa-f]+$/;
|
|
@@ -2922,73 +3518,211 @@ function isValidHexNumber(hex) {
|
|
|
2922
3518
|
// console.log(isValidHexNumber('1A3F')); // true
|
|
2923
3519
|
// console.log(isValidHexNumber('GHIJ')); // false
|
|
2924
3520
|
|
|
3521
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
3522
|
+
retries: 3,
|
|
3523
|
+
retryDelay: 1000,
|
|
3524
|
+
maxRetryDelay: 10000,
|
|
3525
|
+
backoffMultiplier: 2,
|
|
3526
|
+
retryCondition: (error) => {
|
|
3527
|
+
if (error instanceof NetworkError || error instanceof ServerError) {
|
|
3528
|
+
const statusCode = error.statusCode;
|
|
3529
|
+
if (!statusCode)
|
|
3530
|
+
return true;
|
|
3531
|
+
if (statusCode >= 500)
|
|
3532
|
+
return true;
|
|
3533
|
+
if (statusCode === 408 || statusCode === 429)
|
|
3534
|
+
return true;
|
|
3535
|
+
return false;
|
|
3536
|
+
}
|
|
3537
|
+
if (error instanceof Error) {
|
|
3538
|
+
return error.message.includes('timeout') || error.message.includes('network');
|
|
3539
|
+
}
|
|
3540
|
+
return false;
|
|
3541
|
+
},
|
|
3542
|
+
};
|
|
3543
|
+
async function retryWithBackoff(fn, config = {}) {
|
|
3544
|
+
const finalConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
3545
|
+
let lastError;
|
|
3546
|
+
let delay = finalConfig.retryDelay;
|
|
3547
|
+
for (let attempt = 0; attempt <= finalConfig.retries; attempt++) {
|
|
3548
|
+
try {
|
|
3549
|
+
return await fn();
|
|
3550
|
+
}
|
|
3551
|
+
catch (error) {
|
|
3552
|
+
lastError = error;
|
|
3553
|
+
if (attempt === finalConfig.retries) {
|
|
3554
|
+
break;
|
|
3555
|
+
}
|
|
3556
|
+
if (!finalConfig.retryCondition(error)) {
|
|
3557
|
+
throw error;
|
|
3558
|
+
}
|
|
3559
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3560
|
+
delay = Math.min(delay * finalConfig.backoffMultiplier, finalConfig.maxRetryDelay);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
throw lastError;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
/**
|
|
3567
|
+
* A standardized RESTful class that provides a generic interface for performing
|
|
3568
|
+
* CRUD (Create, Read, Update, Delete) operations on a specific API resource. It is designed
|
|
3569
|
+
* to be extended directly from your models. It supports both JSON and FormData requests.
|
|
3570
|
+
*
|
|
3571
|
+
* @example
|
|
3572
|
+
* ```typescript
|
|
3573
|
+
* export class Role extends RestStd {
|
|
3574
|
+
* static override resource = 'roles';
|
|
3575
|
+
* static fetchFn = createAxiosFetcher(axiosInstance);
|
|
3576
|
+
* }
|
|
3577
|
+
*
|
|
3578
|
+
* const roles = await Role.getAll();
|
|
3579
|
+
* ```
|
|
3580
|
+
*/
|
|
2925
3581
|
class RestStd {
|
|
3582
|
+
/**
|
|
3583
|
+
* The resource endpoint. MUST be overridden in subclasses.
|
|
3584
|
+
* @example static override resource = 'users';
|
|
3585
|
+
*/
|
|
2926
3586
|
static resource;
|
|
3587
|
+
/** A flag to determine if request data should be sent as FormData. Defaults to `false`. */
|
|
2927
3588
|
static isFormData = false;
|
|
3589
|
+
/** A record of global headers to be sent with every request. */
|
|
2928
3590
|
static headers = {};
|
|
3591
|
+
/** The function used to make the actual HTTP requests. Optional, defaults to Axios fetcher. */
|
|
2929
3592
|
static fetchFn;
|
|
3593
|
+
/** Retry configuration for failed requests. Optional. */
|
|
3594
|
+
static retryConfig;
|
|
2930
3595
|
/**
|
|
2931
|
-
*
|
|
2932
|
-
* @
|
|
3596
|
+
* Validates that the resource property is defined.
|
|
3597
|
+
* @throws {Error} If resource is not defined
|
|
3598
|
+
*/
|
|
3599
|
+
static validateResource() {
|
|
3600
|
+
if (!this.resource || this.resource.trim() === '') {
|
|
3601
|
+
throw new Error(`[${this.constructor.name}] Static property 'resource' is required. ` +
|
|
3602
|
+
`Please define: static override resource = 'your-resource';`);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Gets the fetcher function, using default if not provided.
|
|
3607
|
+
* Creates a default Axios fetcher if not configured, allowing lazy initialization.
|
|
3608
|
+
* @returns The fetcher function to use
|
|
3609
|
+
*/
|
|
3610
|
+
static getFetchFn() {
|
|
3611
|
+
if (this.fetchFn) {
|
|
3612
|
+
return this.fetchFn;
|
|
3613
|
+
}
|
|
3614
|
+
const axiosInstance = getConfiguredAxiosInstance();
|
|
3615
|
+
return createAxiosFetcher(axiosInstance);
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Executes a fetch request with optional retry logic.
|
|
3619
|
+
* @param config - The fetcher configuration
|
|
3620
|
+
* @returns A promise that resolves with the response data
|
|
3621
|
+
*/
|
|
3622
|
+
static async executeFetch(config) {
|
|
3623
|
+
const fetcher = this.getFetchFn();
|
|
3624
|
+
if (this.retryConfig) {
|
|
3625
|
+
return retryWithBackoff(() => fetcher(config), this.retryConfig);
|
|
3626
|
+
}
|
|
3627
|
+
try {
|
|
3628
|
+
return await fetcher(config);
|
|
3629
|
+
}
|
|
3630
|
+
catch (error) {
|
|
3631
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
3632
|
+
throw NetworkError.fromAxiosError(error);
|
|
3633
|
+
}
|
|
3634
|
+
if (error instanceof Error && error.name === 'TypeError' && error.message?.includes('fetch')) {
|
|
3635
|
+
throw NetworkError.fromFetchError(error);
|
|
3636
|
+
}
|
|
3637
|
+
throw error;
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Builds a URL by combining base URL and suffix.
|
|
3642
|
+
* @param baseUrl - The base URL
|
|
3643
|
+
* @param suffix - Optional suffix to append
|
|
3644
|
+
* @returns The combined URL
|
|
3645
|
+
*/
|
|
3646
|
+
static buildUrl(baseUrl, suffix) {
|
|
3647
|
+
const cleanBase = baseUrl.replace(/\/$/, '');
|
|
3648
|
+
if (suffix) {
|
|
3649
|
+
const cleanSuffix = suffix.startsWith('/') ? suffix : `/${suffix}`;
|
|
3650
|
+
return cleanBase + cleanSuffix;
|
|
3651
|
+
}
|
|
3652
|
+
return cleanBase;
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Sets global headers that will be included in all subsequent requests made by this class.
|
|
3656
|
+
* @param headers - An object containing the headers to be set
|
|
2933
3657
|
*/
|
|
2934
3658
|
static setHeaders(headers) {
|
|
2935
3659
|
this.headers = { ...this.headers, ...headers };
|
|
2936
3660
|
}
|
|
2937
3661
|
/**
|
|
2938
|
-
*
|
|
2939
|
-
* @param data
|
|
2940
|
-
* @returns
|
|
3662
|
+
* Conditionally transforms the request data to FormData if `isFormData` is true.
|
|
3663
|
+
* @param data - The data to be potentially transformed
|
|
3664
|
+
* @returns The transformed data as FormData, or the original data
|
|
2941
3665
|
*/
|
|
2942
3666
|
static transformData(data) {
|
|
2943
3667
|
if (this.isFormData) {
|
|
2944
|
-
|
|
2945
|
-
return formData;
|
|
3668
|
+
return objectToFormData(data);
|
|
2946
3669
|
}
|
|
2947
3670
|
return data;
|
|
2948
3671
|
}
|
|
2949
3672
|
/**
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
2952
|
-
* @
|
|
2953
|
-
* @param options
|
|
2954
|
-
* @returns
|
|
3673
|
+
* Fetches a list of items from the resource's endpoint.
|
|
3674
|
+
* @template TResponse The expected response type
|
|
3675
|
+
* @template TParams The type of query parameters
|
|
3676
|
+
* @param options - Options including params, options, and optional url override
|
|
3677
|
+
* @returns A promise that resolves with the response data
|
|
2955
3678
|
*/
|
|
2956
|
-
static getAll(
|
|
2957
|
-
|
|
3679
|
+
static getAll(options = {}) {
|
|
3680
|
+
this.validateResource();
|
|
3681
|
+
const { params, url } = options;
|
|
3682
|
+
const finalUrl = url || this.resource;
|
|
3683
|
+
const config = {
|
|
2958
3684
|
method: "GET",
|
|
2959
|
-
url:
|
|
3685
|
+
url: finalUrl,
|
|
2960
3686
|
params,
|
|
2961
3687
|
headers: this.headers,
|
|
2962
|
-
}
|
|
3688
|
+
};
|
|
3689
|
+
return this.executeFetch(config);
|
|
2963
3690
|
}
|
|
2964
3691
|
/**
|
|
2965
|
-
*
|
|
2966
|
-
*
|
|
2967
|
-
* @
|
|
2968
|
-
* @param params
|
|
2969
|
-
* @
|
|
2970
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3692
|
+
* Fetches a single item by its ID.
|
|
3693
|
+
* @template TResponse The expected response type
|
|
3694
|
+
* @template TParams The type of query parameters
|
|
3695
|
+
* @param options - Options including id, params, options, and optional url override
|
|
3696
|
+
* @returns A promise that resolves with the response data
|
|
2971
3697
|
*/
|
|
2972
|
-
static getOne(
|
|
2973
|
-
|
|
3698
|
+
static getOne(options) {
|
|
3699
|
+
this.validateResource();
|
|
3700
|
+
const { id, params, url } = options;
|
|
3701
|
+
const baseUrl = url || this.resource;
|
|
3702
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3703
|
+
const config = {
|
|
2974
3704
|
method: "GET",
|
|
2975
|
-
url:
|
|
3705
|
+
url: finalUrl,
|
|
2976
3706
|
params,
|
|
2977
3707
|
headers: this.headers,
|
|
2978
|
-
}
|
|
3708
|
+
};
|
|
3709
|
+
return this.executeFetch(config);
|
|
2979
3710
|
}
|
|
2980
3711
|
/**
|
|
2981
|
-
*
|
|
2982
|
-
*
|
|
2983
|
-
* @
|
|
2984
|
-
* @param options
|
|
2985
|
-
* @returns
|
|
3712
|
+
* Creates a new item.
|
|
3713
|
+
* @template TResponse The expected response type
|
|
3714
|
+
* @template TData The type of data to send
|
|
3715
|
+
* @param options - Options including data, options, and optional url override
|
|
3716
|
+
* @returns A promise that resolves with the response data
|
|
2986
3717
|
*/
|
|
2987
|
-
static create(
|
|
3718
|
+
static create(options) {
|
|
3719
|
+
this.validateResource();
|
|
3720
|
+
const { data, url } = options;
|
|
3721
|
+
const finalUrl = url || this.resource;
|
|
2988
3722
|
const transformedData = this.transformData(data);
|
|
2989
|
-
|
|
3723
|
+
const config = {
|
|
2990
3724
|
method: "POST",
|
|
2991
|
-
url:
|
|
3725
|
+
url: finalUrl,
|
|
2992
3726
|
data: transformedData,
|
|
2993
3727
|
headers: {
|
|
2994
3728
|
...this.headers,
|
|
@@ -2996,22 +3730,27 @@ class RestStd {
|
|
|
2996
3730
|
? ContentTypeEnum.FORM_DATA
|
|
2997
3731
|
: ContentTypeEnum.JSON,
|
|
2998
3732
|
},
|
|
2999
|
-
}
|
|
3733
|
+
};
|
|
3734
|
+
return this.executeFetch(config);
|
|
3000
3735
|
}
|
|
3001
3736
|
/**
|
|
3002
|
-
*
|
|
3003
|
-
*
|
|
3004
|
-
* @
|
|
3005
|
-
* @param options
|
|
3006
|
-
* @returns
|
|
3737
|
+
* Creates multiple new items in a single request.
|
|
3738
|
+
* @template TResponse The expected response type
|
|
3739
|
+
* @template TData The type of data items to send
|
|
3740
|
+
* @param options - Options including data array, options, and optional url override
|
|
3741
|
+
* @returns A promise that resolves with the response data
|
|
3007
3742
|
*/
|
|
3008
|
-
static bulkCreate(
|
|
3743
|
+
static bulkCreate(options) {
|
|
3744
|
+
this.validateResource();
|
|
3745
|
+
const { data, url } = options;
|
|
3746
|
+
const baseUrl = url || this.resource;
|
|
3747
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3009
3748
|
const transformedData = this.isFormData
|
|
3010
3749
|
? data.map((item) => this.transformData(item))
|
|
3011
3750
|
: data;
|
|
3012
|
-
|
|
3751
|
+
const config = {
|
|
3013
3752
|
method: "POST",
|
|
3014
|
-
url:
|
|
3753
|
+
url: finalUrl,
|
|
3015
3754
|
data: transformedData,
|
|
3016
3755
|
headers: {
|
|
3017
3756
|
...this.headers,
|
|
@@ -3019,21 +3758,25 @@ class RestStd {
|
|
|
3019
3758
|
? ContentTypeEnum.FORM_DATA
|
|
3020
3759
|
: ContentTypeEnum.JSON,
|
|
3021
3760
|
},
|
|
3022
|
-
}
|
|
3761
|
+
};
|
|
3762
|
+
return this.executeFetch(config);
|
|
3023
3763
|
}
|
|
3024
3764
|
/**
|
|
3025
|
-
*
|
|
3026
|
-
*
|
|
3027
|
-
* @
|
|
3028
|
-
* @param
|
|
3029
|
-
* @
|
|
3030
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3765
|
+
* Updates an existing item by its ID.
|
|
3766
|
+
* @template TResponse The expected response type
|
|
3767
|
+
* @template TData The type of data to send
|
|
3768
|
+
* @param options - Options including id, data, options, and optional url override
|
|
3769
|
+
* @returns A promise that resolves with the response data
|
|
3031
3770
|
*/
|
|
3032
|
-
static update(
|
|
3771
|
+
static update(options) {
|
|
3772
|
+
this.validateResource();
|
|
3773
|
+
const { id, data, url } = options;
|
|
3774
|
+
const baseUrl = url || this.resource;
|
|
3775
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3033
3776
|
const transformedData = this.transformData(data);
|
|
3034
|
-
|
|
3777
|
+
const config = {
|
|
3035
3778
|
method: "PUT",
|
|
3036
|
-
url:
|
|
3779
|
+
url: finalUrl,
|
|
3037
3780
|
data: transformedData,
|
|
3038
3781
|
headers: {
|
|
3039
3782
|
...this.headers,
|
|
@@ -3041,22 +3784,27 @@ class RestStd {
|
|
|
3041
3784
|
? ContentTypeEnum.FORM_DATA
|
|
3042
3785
|
: ContentTypeEnum.JSON,
|
|
3043
3786
|
},
|
|
3044
|
-
}
|
|
3787
|
+
};
|
|
3788
|
+
return this.executeFetch(config);
|
|
3045
3789
|
}
|
|
3046
3790
|
/**
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
* @
|
|
3050
|
-
* @param options
|
|
3051
|
-
* @returns
|
|
3791
|
+
* Updates multiple existing items in a single request.
|
|
3792
|
+
* @template TResponse The expected response type
|
|
3793
|
+
* @template TData The type of data items to send
|
|
3794
|
+
* @param options - Options including data array, options, and optional url override
|
|
3795
|
+
* @returns A promise that resolves with the response data
|
|
3052
3796
|
*/
|
|
3053
|
-
static bulkUpdate(
|
|
3797
|
+
static bulkUpdate(options) {
|
|
3798
|
+
this.validateResource();
|
|
3799
|
+
const { data, url } = options;
|
|
3800
|
+
const baseUrl = url || this.resource;
|
|
3801
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3054
3802
|
const transformedData = this.isFormData
|
|
3055
3803
|
? data.map((item) => this.transformData(item))
|
|
3056
3804
|
: data;
|
|
3057
|
-
|
|
3805
|
+
const config = {
|
|
3058
3806
|
method: "PUT",
|
|
3059
|
-
url:
|
|
3807
|
+
url: finalUrl,
|
|
3060
3808
|
data: transformedData,
|
|
3061
3809
|
headers: {
|
|
3062
3810
|
...this.headers,
|
|
@@ -3064,21 +3812,25 @@ class RestStd {
|
|
|
3064
3812
|
? ContentTypeEnum.FORM_DATA
|
|
3065
3813
|
: ContentTypeEnum.JSON,
|
|
3066
3814
|
},
|
|
3067
|
-
}
|
|
3815
|
+
};
|
|
3816
|
+
return this.executeFetch(config);
|
|
3068
3817
|
}
|
|
3069
3818
|
/**
|
|
3070
|
-
* Partially
|
|
3071
|
-
*
|
|
3072
|
-
* @
|
|
3073
|
-
* @param
|
|
3074
|
-
* @
|
|
3075
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3819
|
+
* Partially updates an existing item by its ID.
|
|
3820
|
+
* @template TResponse The expected response type
|
|
3821
|
+
* @template TData The type of data to send (partial)
|
|
3822
|
+
* @param options - Options including id, data, options, and optional url override
|
|
3823
|
+
* @returns A promise that resolves with the response data
|
|
3076
3824
|
*/
|
|
3077
|
-
static patch(
|
|
3825
|
+
static patch(options) {
|
|
3826
|
+
this.validateResource();
|
|
3827
|
+
const { id, data, url } = options;
|
|
3828
|
+
const baseUrl = url || this.resource;
|
|
3829
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3078
3830
|
const transformedData = this.transformData(data);
|
|
3079
|
-
|
|
3831
|
+
const config = {
|
|
3080
3832
|
method: "PATCH",
|
|
3081
|
-
url:
|
|
3833
|
+
url: finalUrl,
|
|
3082
3834
|
data: transformedData,
|
|
3083
3835
|
headers: {
|
|
3084
3836
|
...this.headers,
|
|
@@ -3086,65 +3838,80 @@ class RestStd {
|
|
|
3086
3838
|
? ContentTypeEnum.FORM_DATA
|
|
3087
3839
|
: ContentTypeEnum.JSON,
|
|
3088
3840
|
},
|
|
3089
|
-
}
|
|
3841
|
+
};
|
|
3842
|
+
return this.executeFetch(config);
|
|
3090
3843
|
}
|
|
3091
3844
|
/**
|
|
3092
|
-
*
|
|
3093
|
-
*
|
|
3094
|
-
* @param
|
|
3095
|
-
* @
|
|
3096
|
-
* @returns The result of the fetch function (typically a promise).
|
|
3845
|
+
* Deletes an item by its ID.
|
|
3846
|
+
* @template TResponse The expected response type
|
|
3847
|
+
* @param options - Options including id, options, and optional url override
|
|
3848
|
+
* @returns A promise that resolves with the response data
|
|
3097
3849
|
*/
|
|
3098
|
-
static delete(
|
|
3099
|
-
|
|
3850
|
+
static delete(options) {
|
|
3851
|
+
this.validateResource();
|
|
3852
|
+
const { id, url } = options;
|
|
3853
|
+
const baseUrl = url || this.resource;
|
|
3854
|
+
const finalUrl = this.buildUrl(baseUrl, String(id));
|
|
3855
|
+
const config = {
|
|
3100
3856
|
method: "DELETE",
|
|
3101
|
-
url:
|
|
3857
|
+
url: finalUrl,
|
|
3102
3858
|
headers: this.headers,
|
|
3103
|
-
}
|
|
3859
|
+
};
|
|
3860
|
+
return this.executeFetch(config);
|
|
3104
3861
|
}
|
|
3105
3862
|
/**
|
|
3106
|
-
*
|
|
3107
|
-
*
|
|
3108
|
-
* @param
|
|
3109
|
-
* @
|
|
3110
|
-
* @returns The result of the fetch function.
|
|
3863
|
+
* Deletes multiple items by their IDs in a single request.
|
|
3864
|
+
* @template TResponse The expected response type
|
|
3865
|
+
* @param options - Options including ids array, options, and optional url override
|
|
3866
|
+
* @returns A promise that resolves with the response data
|
|
3111
3867
|
*/
|
|
3112
|
-
static bulkDelete(
|
|
3113
|
-
|
|
3868
|
+
static bulkDelete(options) {
|
|
3869
|
+
this.validateResource();
|
|
3870
|
+
const { ids, url } = options;
|
|
3871
|
+
const baseUrl = url || this.resource;
|
|
3872
|
+
const finalUrl = this.buildUrl(baseUrl, 'bulk');
|
|
3873
|
+
const config = {
|
|
3114
3874
|
method: "DELETE",
|
|
3115
|
-
url:
|
|
3875
|
+
url: finalUrl,
|
|
3116
3876
|
data: { ids },
|
|
3117
3877
|
headers: this.headers,
|
|
3118
|
-
}
|
|
3878
|
+
};
|
|
3879
|
+
return this.executeFetch(config);
|
|
3119
3880
|
}
|
|
3120
3881
|
/**
|
|
3121
|
-
*
|
|
3122
|
-
*
|
|
3123
|
-
* @
|
|
3124
|
-
* @param options
|
|
3125
|
-
* @returns
|
|
3882
|
+
* Creates a new item or updates an existing one, based on the presence of an `id` property in the data.
|
|
3883
|
+
* @template TResponse The expected response type
|
|
3884
|
+
* @template TData The type of data to send (must have optional id)
|
|
3885
|
+
* @param options - Options including data, options, and optional url override
|
|
3886
|
+
* @returns A promise that resolves with the response data
|
|
3126
3887
|
*/
|
|
3127
|
-
static upsert(
|
|
3128
|
-
if (data.id) {
|
|
3129
|
-
return this.update(
|
|
3888
|
+
static upsert(options) {
|
|
3889
|
+
if (options.data.id) {
|
|
3890
|
+
return this.update({
|
|
3891
|
+
id: options.data.id,
|
|
3892
|
+
data: options.data,
|
|
3893
|
+
url: options.url,
|
|
3894
|
+
});
|
|
3130
3895
|
}
|
|
3131
3896
|
else {
|
|
3132
|
-
return this.create(
|
|
3897
|
+
return this.create({
|
|
3898
|
+
data: options.data,
|
|
3899
|
+
url: options.url,
|
|
3900
|
+
});
|
|
3133
3901
|
}
|
|
3134
3902
|
}
|
|
3135
3903
|
/**
|
|
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).
|
|
3904
|
+
* Makes a custom HTTP request, providing full flexibility over the method, URL, and data.
|
|
3905
|
+
* @template TResponse The expected response type
|
|
3906
|
+
* @template TParams The type of query parameters
|
|
3907
|
+
* @template TData The type of request body data
|
|
3908
|
+
* @param options - Options including method, url, params, data, and options
|
|
3909
|
+
* @returns A promise that resolves with the response data
|
|
3144
3910
|
*/
|
|
3145
|
-
static customRequest(
|
|
3911
|
+
static customRequest(options) {
|
|
3912
|
+
const { method, url, params, data } = options;
|
|
3146
3913
|
const transformedData = this.transformData(data);
|
|
3147
|
-
|
|
3914
|
+
const config = {
|
|
3148
3915
|
method: method,
|
|
3149
3916
|
url: url,
|
|
3150
3917
|
params: params,
|
|
@@ -3155,17 +3922,20 @@ class RestStd {
|
|
|
3155
3922
|
? ContentTypeEnum.FORM_DATA
|
|
3156
3923
|
: ContentTypeEnum.JSON,
|
|
3157
3924
|
},
|
|
3158
|
-
}
|
|
3925
|
+
};
|
|
3926
|
+
return this.executeFetch(config);
|
|
3159
3927
|
}
|
|
3160
3928
|
}
|
|
3161
3929
|
|
|
3162
3930
|
/**
|
|
3163
|
-
*
|
|
3931
|
+
* A composable function that executes an Axios request and returns the response data.
|
|
3932
|
+
* It simplifies making API calls by wrapping the Axios request in a reusable function.
|
|
3164
3933
|
*
|
|
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
|
|
3934
|
+
* @template T The expected type of the response data.
|
|
3935
|
+
* @param {AxiosInstance} axios - The Axios instance to use for making the request.
|
|
3936
|
+
* @param {AxiosRequestConfig} axiosRequest - The configuration for the Axios request (e.g., URL, method, headers).
|
|
3937
|
+
* @returns {Promise<T>} A promise that resolves with the data from the Axios response.
|
|
3938
|
+
* @throws {Error} Throws an error if the Axios request fails.
|
|
3169
3939
|
*/
|
|
3170
3940
|
async function axiosFetch(axios, axiosRequest) {
|
|
3171
3941
|
return axios(axiosRequest)
|
|
@@ -3176,10 +3946,19 @@ async function axiosFetch(axios, axiosRequest) {
|
|
|
3176
3946
|
}
|
|
3177
3947
|
|
|
3178
3948
|
/**
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3181
|
-
*
|
|
3182
|
-
*
|
|
3949
|
+
* A factory function that creates a reusable query function for making API requests.
|
|
3950
|
+
* It abstracts the Axios instance creation and allows for a custom instance to be provided.
|
|
3951
|
+
* This is particularly useful for creating typed query functions for use with libraries like Vue Query.
|
|
3952
|
+
*
|
|
3953
|
+
* @template TQueryFnData The expected data type of the query function's response.
|
|
3954
|
+
* @template TData The expected data type of the transformed data.
|
|
3955
|
+
* @param {Function} fetchFn - The underlying function that will be called to perform the fetch operation.
|
|
3956
|
+
* This function should accept an Axios instance, Axios request configuration, and optional query options.
|
|
3957
|
+
* @param {AxiosInstance} [axiosCustomInstance] - An optional custom Axios instance to use for the request.
|
|
3958
|
+
* If not provided, a default configured instance will be used.
|
|
3959
|
+
* @returns {(axiosRequestConfig: AxiosRequestConfig, options?: UseQueryOptions<TQueryFnData, Error, TData>) => any}
|
|
3960
|
+
* A new function that takes Axios request configuration and optional query options, and when executed,
|
|
3961
|
+
* performs the API request using the configured `fetchFn`.
|
|
3183
3962
|
*/
|
|
3184
3963
|
function useFetch(fetchFn, axiosCustomInstance) {
|
|
3185
3964
|
const instance = axiosCustomInstance || getConfiguredAxiosInstance();
|
|
@@ -3188,131 +3967,20 @@ function useFetch(fetchFn, axiosCustomInstance) {
|
|
|
3188
3967
|
};
|
|
3189
3968
|
}
|
|
3190
3969
|
|
|
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
3970
|
let notified = false;
|
|
3312
3971
|
/**
|
|
3313
|
-
*
|
|
3972
|
+
* A composable that provides a reactive interface to Tailwind CSS breakpoints using `@vueuse/core`.
|
|
3973
|
+
* It simplifies working with responsive layouts by offering a set of reactive booleans for different
|
|
3974
|
+
* screen sizes and combinations. On its first invocation, it also logs the current device type (Mobile,
|
|
3975
|
+
* Tablet, Laptop, or Desktop) to the console for easier debugging during development.
|
|
3314
3976
|
*
|
|
3315
|
-
* @returns {
|
|
3977
|
+
* @returns {object} An object containing various reactive properties for screen sizes, window dimensions, and breakpoint utilities, including:
|
|
3978
|
+
* - `current`: A ref to the current breakpoint name.
|
|
3979
|
+
* - `active`: A ref to the currently active breakpoint name.
|
|
3980
|
+
* - `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.
|
|
3981
|
+
* - `mobile`, `tablet`, `laptop`, `desktop`: Refs that are true for common device width ranges.
|
|
3982
|
+
* - `windowWidth`, `windowHeight`: Reactive refs for the window's width and height.
|
|
3983
|
+
* - `breakpoints`: The original `useBreakpoints` return object from `@vueuse/core`.
|
|
3316
3984
|
*/
|
|
3317
3985
|
function useBreakpoint() {
|
|
3318
3986
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
@@ -3424,15 +4092,20 @@ function useBreakpoint() {
|
|
|
3424
4092
|
}
|
|
3425
4093
|
|
|
3426
4094
|
/**
|
|
3427
|
-
* A composable function that filters objects based on a
|
|
4095
|
+
* A composable function that filters an array of objects based on a specified field, data type, and criteria.
|
|
4096
|
+
* It supports filtering by date range, string matching (case-insensitive and diacritic-insensitive), number range, and boolean values.
|
|
3428
4097
|
*
|
|
3429
|
-
* @
|
|
3430
|
-
* @param {
|
|
3431
|
-
* @param {
|
|
3432
|
-
* @param {string} filterConfig.
|
|
3433
|
-
* @param {
|
|
3434
|
-
*
|
|
3435
|
-
*
|
|
4098
|
+
* @template T A generic type that extends a record of string keys to any value, representing the objects in the array.
|
|
4099
|
+
* @param {T[]} items - The array of objects to be filtered.
|
|
4100
|
+
* @param {object} filterConfig - The configuration object for filtering.
|
|
4101
|
+
* @param {string} filterConfig.field - The name of the field in the objects to filter by.
|
|
4102
|
+
* @param {'date' | 'string' | 'number' | 'boolean'} filterConfig.type - The data type of the field to be filtered.
|
|
4103
|
+
* @param {any} filterConfig.criteria - The criteria for filtering, which varies based on the `type`:
|
|
4104
|
+
* - For 'date': An object `{ startDate: string, endDate: string }`.
|
|
4105
|
+
* - For 'string': A string to search for.
|
|
4106
|
+
* - For 'number': An object `{ min: number, max: number }`.
|
|
4107
|
+
* - For 'boolean': A boolean value.
|
|
4108
|
+
* @returns {T[]} The filtered array of objects. If the criteria are invalid or not provided, the original array is returned.
|
|
3436
4109
|
*/
|
|
3437
4110
|
function useFilter(items, filterConfig) {
|
|
3438
4111
|
// Ensure the filterConfig is correctly structured
|
|
@@ -3481,12 +4154,20 @@ function useFilter(items, filterConfig) {
|
|
|
3481
4154
|
}
|
|
3482
4155
|
|
|
3483
4156
|
/**
|
|
3484
|
-
*
|
|
4157
|
+
* A composable that provides pagination logic based on reactive refs for the current page,
|
|
4158
|
+
* total number of items, and items per page.
|
|
3485
4159
|
*
|
|
3486
|
-
* @param page -
|
|
3487
|
-
* @param total -
|
|
3488
|
-
* @param pageSize -
|
|
3489
|
-
* @returns
|
|
4160
|
+
* @param {Ref<number>} page - A reactive ref representing the current page number.
|
|
4161
|
+
* @param {Ref<number>} total - A reactive ref representing the total number of items to be paginated.
|
|
4162
|
+
* @param {Ref<number>} pageSize - A reactive ref representing the number of items per page.
|
|
4163
|
+
* @returns {{
|
|
4164
|
+
* totalPages: import('vue').ComputedRef<number>,
|
|
4165
|
+
* canFetchNextPage: () => boolean,
|
|
4166
|
+
* canFetchPreviousPage: () => boolean
|
|
4167
|
+
* }} An object containing:
|
|
4168
|
+
* - `totalPages`: A computed property that calculates the total number of pages.
|
|
4169
|
+
* - `canFetchNextPage`: A function that returns `true` if there is a next page.
|
|
4170
|
+
* - `canFetchPreviousPage`: A function that returns `true` if there is a previous page.
|
|
3490
4171
|
*/
|
|
3491
4172
|
function usePagination(page, total, pageSize) {
|
|
3492
4173
|
// Calculate the total number of pages
|
|
@@ -3503,11 +4184,16 @@ function usePagination(page, total, pageSize) {
|
|
|
3503
4184
|
}
|
|
3504
4185
|
|
|
3505
4186
|
/**
|
|
3506
|
-
*
|
|
3507
|
-
*
|
|
3508
|
-
*
|
|
3509
|
-
* @
|
|
3510
|
-
* @
|
|
4187
|
+
* A composable that sorts an array of objects based on a selected criterion from a list of predefined sorting options.
|
|
4188
|
+
* It supports sorting by number, date, boolean, and string fields, in both ascending and descending order.
|
|
4189
|
+
*
|
|
4190
|
+
* @template T The type of items in the array.
|
|
4191
|
+
* @param {T[]} items - The array of objects to sort.
|
|
4192
|
+
* @param {Array<{value: number, label: string, field: string, order: string, type: string}>} criteriaList - A list of
|
|
4193
|
+
* sorting criteria objects. Each object defines a sorting option with a unique `value`, a `label` for display, the `field`
|
|
4194
|
+
* to sort by, the `order` ('asc' or 'desc'), and the data `type` ('number', 'date', 'boolean', 'string').
|
|
4195
|
+
* @param {number} selectedCriteria - The `value` of the currently selected sorting criterion from the `criteriaList`.
|
|
4196
|
+
* @returns {ComputedRef<T[]>} A computed ref containing the sorted items. If the selected criterion is not found, the original array is returned.
|
|
3511
4197
|
*/
|
|
3512
4198
|
function useSorter(items, criteriaList, selectedCriteria) {
|
|
3513
4199
|
return computed(() => {
|
|
@@ -3539,36 +4225,58 @@ function useSorter(items, criteriaList, selectedCriteria) {
|
|
|
3539
4225
|
return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
|
|
3540
4226
|
}
|
|
3541
4227
|
});
|
|
3542
|
-
})
|
|
4228
|
+
});
|
|
3543
4229
|
}
|
|
3544
4230
|
|
|
3545
4231
|
/**
|
|
3546
4232
|
* Custom hook for authentication logic, including login, logout, token management, and session preference.
|
|
4233
|
+
* Accepts an optional fetcher function. If not provided, uses the default configured fetcher or falls back to Axios.
|
|
4234
|
+
*
|
|
4235
|
+
* @param {Fetcher} [fetcher] - Optional fetcher function to use for auth requests. If not provided, uses the default configured fetcher.
|
|
4236
|
+
* @returns {{
|
|
4237
|
+
* logout: (params?: Record<string, unknown>) => Promise<void>,
|
|
4238
|
+
* login: (params: Record<string, unknown>, persistence: LocationPreference, tokenPaths?: AuthTokenPaths) => Promise<AuthResponse>
|
|
4239
|
+
* }} An object containing authentication functions.
|
|
3547
4240
|
*
|
|
3548
|
-
* @
|
|
3549
|
-
*
|
|
4241
|
+
* @example
|
|
4242
|
+
* ```typescript
|
|
4243
|
+
* // Using default fetcher (Axios)
|
|
4244
|
+
* const auth = useAuth();
|
|
4245
|
+
*
|
|
4246
|
+
* // Using custom fetcher
|
|
4247
|
+
* const customFetcher = createOfetchFetcher();
|
|
4248
|
+
* const auth = useAuth(customFetcher);
|
|
4249
|
+
* ```
|
|
3550
4250
|
*/
|
|
3551
|
-
function useAuth() {
|
|
3552
|
-
const axiosInstance = getConfiguredAxiosInstance();
|
|
4251
|
+
function useAuth(fetcher) {
|
|
3553
4252
|
const endpoints = getEndpointsConfig();
|
|
4253
|
+
const getFetcher = () => {
|
|
4254
|
+
return fetcher || getDefaultAuthFetcher();
|
|
4255
|
+
};
|
|
3554
4256
|
/**
|
|
3555
4257
|
* Logs out the user by making a POST request to the logout endpoint,
|
|
3556
4258
|
* cleaning all stored credentials, and reloading the page.
|
|
3557
4259
|
* The session persistence preference is NOT reset here; it persists across logouts.
|
|
3558
4260
|
*
|
|
3559
|
-
* @param {
|
|
4261
|
+
* @param {Record<string, unknown>} [params={}] - Optional parameters to send with the logout request.
|
|
3560
4262
|
* @returns {Promise<void>}
|
|
3561
4263
|
*/
|
|
3562
4264
|
const logout = async (params = {}) => {
|
|
3563
4265
|
try {
|
|
3564
|
-
await
|
|
4266
|
+
await getFetcher()({
|
|
4267
|
+
method: 'POST',
|
|
4268
|
+
url: endpoints.LOGOUT,
|
|
4269
|
+
data: params,
|
|
4270
|
+
});
|
|
3565
4271
|
}
|
|
3566
4272
|
catch (error) {
|
|
3567
|
-
handleError(error
|
|
4273
|
+
handleError(error);
|
|
3568
4274
|
}
|
|
3569
4275
|
finally {
|
|
3570
4276
|
await cleanCredentials(await getSessionPersistence());
|
|
3571
|
-
window
|
|
4277
|
+
if (typeof window !== 'undefined') {
|
|
4278
|
+
window.location.reload();
|
|
4279
|
+
}
|
|
3572
4280
|
}
|
|
3573
4281
|
};
|
|
3574
4282
|
/**
|
|
@@ -3583,7 +4291,11 @@ function useAuth() {
|
|
|
3583
4291
|
*/
|
|
3584
4292
|
const login = async (params = {}, persistence, tokenPaths = getTokenPathsConfig()) => {
|
|
3585
4293
|
try {
|
|
3586
|
-
const
|
|
4294
|
+
const data = await getFetcher()({
|
|
4295
|
+
method: 'POST',
|
|
4296
|
+
url: endpoints.LOGIN,
|
|
4297
|
+
data: params,
|
|
4298
|
+
});
|
|
3587
4299
|
const { accessToken, refreshToken } = extractAndValidateTokens(data, tokenPaths, "LOGIN");
|
|
3588
4300
|
configSession({
|
|
3589
4301
|
persistencePreference: persistence,
|
|
@@ -3592,7 +4304,7 @@ function useAuth() {
|
|
|
3592
4304
|
return data;
|
|
3593
4305
|
}
|
|
3594
4306
|
catch (error) {
|
|
3595
|
-
handleError(error
|
|
4307
|
+
handleError(error);
|
|
3596
4308
|
throw error;
|
|
3597
4309
|
}
|
|
3598
4310
|
};
|
|
@@ -3602,17 +4314,63 @@ function useAuth() {
|
|
|
3602
4314
|
};
|
|
3603
4315
|
}
|
|
3604
4316
|
|
|
4317
|
+
// @ts-ignore - ofetch is an optional peer dependency
|
|
4318
|
+
/**
|
|
4319
|
+
* Creates a fetcher function using ofetch.
|
|
4320
|
+
*
|
|
4321
|
+
* @param baseURL - Optional base URL for requests
|
|
4322
|
+
* @param defaultOptions - Optional default options for ofetch
|
|
4323
|
+
* @returns A fetcher function compatible with RestStd
|
|
4324
|
+
*
|
|
4325
|
+
* @example
|
|
4326
|
+
* ```typescript
|
|
4327
|
+
* import { createOfetchFetcher, RestStd } from '@arex95/vue-core';
|
|
4328
|
+
*
|
|
4329
|
+
* export class Role extends RestStd {
|
|
4330
|
+
* static override resource = 'roles';
|
|
4331
|
+
* static fetchFn = createOfetchFetcher('https://api.example.com');
|
|
4332
|
+
* }
|
|
4333
|
+
* ```
|
|
4334
|
+
*
|
|
4335
|
+
* @example
|
|
4336
|
+
* ```typescript
|
|
4337
|
+
* import { createFetch } from 'ofetch';
|
|
4338
|
+
* import { createOfetchFetcher, RestStd } from '@arex95/vue-core';
|
|
4339
|
+
*
|
|
4340
|
+
* const ofetchInstance = createFetch({ baseURL: 'https://api.example.com' });
|
|
4341
|
+
*
|
|
4342
|
+
* export class Role extends RestStd {
|
|
4343
|
+
* static override resource = 'roles';
|
|
4344
|
+
* static fetchFn = createOfetchFetcher(undefined, { fetch: ofetchInstance });
|
|
4345
|
+
* }
|
|
4346
|
+
* ```
|
|
4347
|
+
*/
|
|
4348
|
+
function createOfetchFetcher(baseURL, defaultOptions) {
|
|
4349
|
+
return async (config) => {
|
|
4350
|
+
const url = baseURL
|
|
4351
|
+
? `${baseURL.replace(/\/$/, '')}/${config.url.replace(/^\//, '')}`
|
|
4352
|
+
: config.url;
|
|
4353
|
+
return $fetch(url, {
|
|
4354
|
+
method: config.method,
|
|
4355
|
+
query: config.params,
|
|
4356
|
+
body: config.data,
|
|
4357
|
+
headers: config.headers,
|
|
4358
|
+
...defaultOptions,
|
|
4359
|
+
});
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
|
|
3605
4363
|
/**
|
|
3606
|
-
*
|
|
3607
|
-
*
|
|
4364
|
+
* A Vue plugin that serves as the entry point for the `@arex95/vue-core` library.
|
|
4365
|
+
* It initializes and configures all the core modules, such as authentication, API communication,
|
|
4366
|
+
* and token management, based on the provided options.
|
|
3608
4367
|
*/
|
|
3609
4368
|
const ArexVueCore = {
|
|
3610
4369
|
/**
|
|
3611
|
-
* The `install` method
|
|
3612
|
-
* It is automatically called when `app.use(ArexVueCore, options)` is executed.
|
|
4370
|
+
* The `install` method required by Vue's plugin system. It is called when `app.use()` is invoked.
|
|
3613
4371
|
*
|
|
3614
|
-
* @param app The Vue application instance.
|
|
3615
|
-
* @param options The configuration
|
|
4372
|
+
* @param {App} app - The Vue application instance.
|
|
4373
|
+
* @param {ArexVueCoreOptions} options - The configuration object for the library.
|
|
3616
4374
|
*/
|
|
3617
4375
|
install: (app, options) => {
|
|
3618
4376
|
if (!options) {
|
|
@@ -3648,4 +4406,4 @@ const ArexVueCore = {
|
|
|
3648
4406
|
},
|
|
3649
4407
|
};
|
|
3650
4408
|
|
|
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,
|
|
4409
|
+
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 };
|