@arex95/vue-core 1.1.34 → 1.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +498 -507
- package/package.json +76 -76
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,182 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
+
import { jwtDecode } from 'jwt-decode';
|
|
2
3
|
import { useRouter } from 'vue-router';
|
|
3
4
|
import { v4 } from 'uuid';
|
|
4
5
|
import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
|
|
5
|
-
import { jwtDecode } from 'jwt-decode';
|
|
6
6
|
import { useQuery } from '@tanstack/vue-query';
|
|
7
7
|
import { ref, watch, onServerPrefetch, onMounted, computed } from 'vue';
|
|
8
8
|
|
|
9
|
+
let tokensConfig = Object.freeze({
|
|
10
|
+
ACCESS_TOKEN: "access_token",
|
|
11
|
+
REFRESH_TOKEN: "refresh_token",
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Configures the global keys for access and refresh tokens.
|
|
15
|
+
* Once set, they cannot be modified.
|
|
16
|
+
*
|
|
17
|
+
* @param {TokenKeyConfig} config - An object containing the token keys.
|
|
18
|
+
* @param {string} config.accessTokenKey - The name of the key for the access token.
|
|
19
|
+
* @param {string} config.refreshTokenKey - The name of the key for the refresh token.
|
|
20
|
+
*
|
|
21
|
+
* @returns {void} Does not return anything, but freezes the token configuration object.
|
|
22
|
+
*/
|
|
23
|
+
function configTokenKeys(config) {
|
|
24
|
+
tokensConfig = Object.freeze({
|
|
25
|
+
ACCESS_TOKEN: config.accessTokenKey,
|
|
26
|
+
REFRESH_TOKEN: config.refreshTokenKey,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves the current token configuration.
|
|
31
|
+
*
|
|
32
|
+
* @returns {TokensConfig} The configuration of the access and refresh token keys.
|
|
33
|
+
*/
|
|
34
|
+
function getTokenConfig() {
|
|
35
|
+
return tokensConfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Converts an ArrayBuffer or Uint8Array to a hexadecimal string.
|
|
40
|
+
* @param buffer The ArrayBuffer or Uint8Array to convert.
|
|
41
|
+
* @returns The hexadecimal string.
|
|
42
|
+
*/
|
|
43
|
+
function ab2hex(buffer) {
|
|
44
|
+
return Array.from(new Uint8Array(buffer))
|
|
45
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
46
|
+
.join("");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Converts a hexadecimal string to a Uint8Array.
|
|
50
|
+
* @param hex The hexadecimal string to convert.
|
|
51
|
+
* @returns The Uint8Array.
|
|
52
|
+
* @throws {TypeError} If the input is not a string.
|
|
53
|
+
* @throws {Error} If the hexadecimal string format is invalid or has an odd length.
|
|
54
|
+
*/
|
|
55
|
+
function hex2ab(hex) {
|
|
56
|
+
if (typeof hex !== "string") {
|
|
57
|
+
throw new TypeError("Input must be a string.");
|
|
58
|
+
}
|
|
59
|
+
if (hex.length === 0) {
|
|
60
|
+
return new Uint8Array();
|
|
61
|
+
}
|
|
62
|
+
if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
|
|
63
|
+
throw new Error("Invalid hexadecimal string format or odd length.");
|
|
64
|
+
}
|
|
65
|
+
const array = new Uint8Array(hex.length / 2);
|
|
66
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
67
|
+
array[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
68
|
+
}
|
|
69
|
+
return array;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Derives an encryption key from a secret key.
|
|
73
|
+
* @param secretKey The secret key in plain text.
|
|
74
|
+
* @returns A promise that resolves with the derived CryptoKey.
|
|
75
|
+
* @throws {Error} If the secretKey is null or empty.
|
|
76
|
+
*/
|
|
77
|
+
async function importKey(secretKey) {
|
|
78
|
+
if (!secretKey) {
|
|
79
|
+
throw new Error("Secret key cannot be null or empty.");
|
|
80
|
+
}
|
|
81
|
+
const keyMaterial = new TextEncoder().encode(secretKey);
|
|
82
|
+
const digest = await crypto.subtle.digest("SHA-256", keyMaterial);
|
|
83
|
+
return crypto.subtle.importKey("raw", digest, { name: "AES-CBC", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Encrypts a value with the provided secret key.
|
|
87
|
+
* @param value The value to encrypt.
|
|
88
|
+
* @param secretKey The secret key for encryption.
|
|
89
|
+
* @returns A promise that resolves with the IV (hex) + ciphertext (hex) string.
|
|
90
|
+
* @throws {Error} If the secretKey is null or empty (via importKey).
|
|
91
|
+
*/
|
|
92
|
+
async function encrypt(value, secretKey) {
|
|
93
|
+
const key = await importKey(secretKey);
|
|
94
|
+
const iv = crypto.getRandomValues(new Uint8Array(16));
|
|
95
|
+
const encodedValue = new TextEncoder().encode(value);
|
|
96
|
+
const ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, key, encodedValue);
|
|
97
|
+
return ab2hex(iv) + ab2hex(new Uint8Array(ciphertext));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Decrypts an encrypted value.
|
|
101
|
+
* @param encryptedValue The encrypted string (IV_hex + ciphertext_hex).
|
|
102
|
+
* @param secretKey The secret key for decryption.
|
|
103
|
+
* @returns A promise that resolves with the decrypted value.
|
|
104
|
+
* @throws {Error} If encryptedValue is null or empty, too short,
|
|
105
|
+
* or if the IV/ciphertext have incorrect lengths after conversion.
|
|
106
|
+
* @throws {Error} If the secretKey is null or empty (via importKey).
|
|
107
|
+
* @throws {TypeError} If hex2ab receives an invalid input type.
|
|
108
|
+
*/
|
|
109
|
+
async function decrypt(encryptedValue, secretKey) {
|
|
110
|
+
if (!encryptedValue) {
|
|
111
|
+
throw new Error("Encrypted value cannot be null or empty.");
|
|
112
|
+
}
|
|
113
|
+
// For AES-CBC, the IV is ALWAYS 16 bytes.
|
|
114
|
+
// 16 bytes * 2 hex characters/byte = 32 hex characters for the IV.
|
|
115
|
+
if (encryptedValue.length < 32) {
|
|
116
|
+
throw new Error("Encrypted value is too short. Expected at least 32 hexadecimal characters for the IV.");
|
|
117
|
+
}
|
|
118
|
+
const key = await importKey(secretKey);
|
|
119
|
+
const ivHex = encryptedValue.substring(0, 32);
|
|
120
|
+
const ciphertextHex = encryptedValue.substring(32);
|
|
121
|
+
const iv = hex2ab(ivHex);
|
|
122
|
+
const ciphertext = hex2ab(ciphertextHex);
|
|
123
|
+
if (iv.byteLength !== 16) {
|
|
124
|
+
throw new Error(`Converted IV has incorrect length: ${iv.byteLength} bytes. Expected 16 bytes.`);
|
|
125
|
+
}
|
|
126
|
+
if (ciphertext.byteLength === 0) {
|
|
127
|
+
throw new Error("Ciphertext is empty. No data to decrypt.");
|
|
128
|
+
}
|
|
129
|
+
const decryptedBuffer = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, ciphertext);
|
|
130
|
+
return new TextDecoder().decode(decryptedBuffer);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Encrypts and stores an item in local or session storage.
|
|
135
|
+
* Assumes the `window` environment is available.
|
|
136
|
+
* @param key The key under which to store the value.
|
|
137
|
+
* @param value The value to encrypt and store.
|
|
138
|
+
* @param secretKey The secret key for encryption.
|
|
139
|
+
* @param location Determines where the item is stored: 'local' for localStorage, 'session' for sessionStorage.
|
|
140
|
+
* @returns A promise that resolves when the item is stored. Throws an error if it fails.
|
|
141
|
+
*/
|
|
142
|
+
async function storeEncryptedItem(key, value, secretKey, location) {
|
|
143
|
+
if (typeof window === "undefined") {
|
|
144
|
+
throw new Error("Cannot access storage: window is not defined.");
|
|
145
|
+
}
|
|
146
|
+
const storage = location === "local" ? window.localStorage : window.sessionStorage;
|
|
147
|
+
const encryptedValue = await encrypt(value, secretKey);
|
|
148
|
+
storage.setItem(key, encryptedValue);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Retrieves and decrypts a value from local or session storage.
|
|
152
|
+
* Assumes the `window` environment is available.
|
|
153
|
+
* @param key The key of the item to retrieve.
|
|
154
|
+
* @param secretKey The secret key for decryption.
|
|
155
|
+
* @param location Specifies where to search for the item: 'local' for localStorage, 'session' for sessionStorage, or 'any' to check both (session first).
|
|
156
|
+
* @returns A promise that resolves with the decrypted value or null if not found or decryption fails.
|
|
157
|
+
*/
|
|
158
|
+
async function getDecryptedItem(key, secretKey, location) {
|
|
159
|
+
if (typeof window === "undefined") {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
let encryptedData = null;
|
|
163
|
+
if (location === "session" || location === "any") {
|
|
164
|
+
encryptedData = window.sessionStorage.getItem(key);
|
|
165
|
+
}
|
|
166
|
+
if (!encryptedData && (location === "local" || location === "any")) {
|
|
167
|
+
encryptedData = window.localStorage.getItem(key);
|
|
168
|
+
}
|
|
169
|
+
if (!encryptedData) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
return await decrypt(encryptedData, secretKey);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
9
180
|
/**
|
|
10
181
|
* Enum defining available screen sizes.
|
|
11
182
|
*/
|
|
@@ -538,68 +709,288 @@ function inferErrorType(error) {
|
|
|
538
709
|
return 'error';
|
|
539
710
|
}
|
|
540
711
|
|
|
541
|
-
let tokensConfig = Object.freeze({
|
|
542
|
-
ACCESS_TOKEN: "access_token",
|
|
543
|
-
REFRESH_TOKEN: "refresh_token",
|
|
544
|
-
});
|
|
545
712
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* @param {TokenKeyConfig} config - An object containing the token keys.
|
|
550
|
-
* @param {string} config.accessTokenKey - The name of the key for the access token.
|
|
551
|
-
* @param {string} config.refreshTokenKey - The name of the key for the refresh token.
|
|
713
|
+
* Clears all stored authentication data (access and refresh tokens)
|
|
714
|
+
* from either sessionStorage, localStorage, or both based on the provided location preference.
|
|
552
715
|
*
|
|
553
|
-
* @
|
|
716
|
+
* @param {LocationPreference} location - The storage preference ('local' for localStorage, 'session' for sessionStorage, 'any' for both).
|
|
717
|
+
* @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
|
|
554
718
|
*/
|
|
555
|
-
|
|
556
|
-
tokensConfig =
|
|
557
|
-
|
|
558
|
-
|
|
719
|
+
const cleanCredentials = async (location) => {
|
|
720
|
+
const tokensConfig = getTokenConfig();
|
|
721
|
+
Object.keys(tokensConfig).forEach((key) => {
|
|
722
|
+
const itemKey = tokensConfig[key];
|
|
723
|
+
if (location === "local" || location === "any") {
|
|
724
|
+
localStorage.removeItem(itemKey);
|
|
725
|
+
}
|
|
726
|
+
if (location === "session" || location === "any") {
|
|
727
|
+
sessionStorage.removeItem(itemKey);
|
|
728
|
+
}
|
|
559
729
|
});
|
|
560
|
-
}
|
|
730
|
+
};
|
|
561
731
|
/**
|
|
562
|
-
* Retrieves the
|
|
732
|
+
* Retrieves the authentication token (access token) from storage, decrypting it
|
|
733
|
+
* using the provided secret key and based on the specified session preference.
|
|
563
734
|
*
|
|
564
|
-
* @
|
|
735
|
+
* @param {string} secretKey - The secret key used for decryption.
|
|
736
|
+
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
737
|
+
* @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
|
|
565
738
|
*/
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
let endpointsConfig = {
|
|
571
|
-
LOGIN: "/login",
|
|
572
|
-
REFRESH: "/refresh",
|
|
573
|
-
LOGOUT: "/logout",
|
|
739
|
+
const getAuthToken = async (secretKey, location) => {
|
|
740
|
+
const tokensConfig = getTokenConfig();
|
|
741
|
+
return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
|
|
574
742
|
};
|
|
575
743
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
* @param {EndpointConfig} config - An object containing the authentication endpoint URLs.
|
|
580
|
-
* @param {string} config.loginEndpoint - URL of the login endpoint.
|
|
581
|
-
* @param {string} config.refreshEndpoint - URL of the refresh token endpoint.
|
|
582
|
-
* @param {string} config.logoutEndpoint - URL of the logout endpoint.
|
|
744
|
+
* Retrieves the authentication refresh token from storage, decrypting it
|
|
745
|
+
* using the provided secret key and based on the specified session preference.
|
|
583
746
|
*
|
|
584
|
-
* @
|
|
747
|
+
* @param {string} secretKey - The secret key used for decryption.
|
|
748
|
+
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
749
|
+
* @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
|
|
585
750
|
*/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
LOGOUT: config.logoutEndpoint,
|
|
591
|
-
});
|
|
592
|
-
}
|
|
751
|
+
const getAuthRefreshToken = async (secretKey, location) => {
|
|
752
|
+
const tokensConfig = getTokenConfig();
|
|
753
|
+
return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
|
|
754
|
+
};
|
|
593
755
|
/**
|
|
594
|
-
*
|
|
756
|
+
* Stores the authentication token (access token) in storage after encrypting it,
|
|
757
|
+
* based on the specified session preference.
|
|
595
758
|
*
|
|
596
|
-
* @
|
|
597
|
-
* @
|
|
598
|
-
* @
|
|
599
|
-
* @
|
|
600
|
-
*/
|
|
601
|
-
|
|
602
|
-
|
|
759
|
+
* @param {string} token - The access token to store.
|
|
760
|
+
* @param {string} secretKey - The secret key used for encryption.
|
|
761
|
+
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
762
|
+
* @returns {Promise<void>} A promise that resolves when the token is successfully stored.
|
|
763
|
+
*/
|
|
764
|
+
const storeAuthToken = async (token, secretKey, location) => {
|
|
765
|
+
const tokensConfig = getTokenConfig();
|
|
766
|
+
await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
|
|
767
|
+
};
|
|
768
|
+
/**
|
|
769
|
+
* Stores the authentication refresh token in storage after encrypting it,
|
|
770
|
+
* based on the specified session preference.
|
|
771
|
+
*
|
|
772
|
+
* @param {string} token - The refresh token to store.
|
|
773
|
+
* @param {string} secretKey - The secret key used for encryption.
|
|
774
|
+
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
775
|
+
* @returns {Promise<void>} A promise that resolves when the token is successfully stored.
|
|
776
|
+
*/
|
|
777
|
+
const storeAuthRefreshToken = async (token, secretKey, location) => {
|
|
778
|
+
const tokensConfig = getTokenConfig();
|
|
779
|
+
await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Verifies the validity and expiration of the current authentication token.
|
|
783
|
+
* If the token is missing, invalid, or expired, appropriate errors are thrown and credentials are cleaned.
|
|
784
|
+
*
|
|
785
|
+
* @returns {Promise<boolean>} True if the token is valid and unexpired.
|
|
786
|
+
* @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
|
|
787
|
+
* "TOKEN_INVALID" if the token format is invalid.
|
|
788
|
+
*/
|
|
789
|
+
const verifyAuth = async () => {
|
|
790
|
+
const sessionPersistence = 'any';
|
|
791
|
+
const handleAuthError = async (message, shouldClean = true) => {
|
|
792
|
+
handleError(message, false);
|
|
793
|
+
if (shouldClean) {
|
|
794
|
+
await cleanCredentials(sessionPersistence);
|
|
795
|
+
}
|
|
796
|
+
return false;
|
|
797
|
+
};
|
|
798
|
+
try {
|
|
799
|
+
const token = await getAuthToken(getAppKey(), sessionPersistence);
|
|
800
|
+
if (!token) {
|
|
801
|
+
return await handleAuthError("TOKEN_MISSING: No valid token found");
|
|
802
|
+
}
|
|
803
|
+
let decoded;
|
|
804
|
+
try {
|
|
805
|
+
decoded = jwtDecode(token);
|
|
806
|
+
}
|
|
807
|
+
catch (decodeError) {
|
|
808
|
+
return await handleAuthError("TOKEN_INVALID: Invalid token format");
|
|
809
|
+
}
|
|
810
|
+
const currentTime = Date.now() / 1000;
|
|
811
|
+
if (typeof decoded.exp !== "number") {
|
|
812
|
+
return await handleAuthError("TOKEN_INVALID: Invalid expiration format");
|
|
813
|
+
}
|
|
814
|
+
if (decoded.exp <= currentTime) {
|
|
815
|
+
return await handleAuthError("TOKEN_EXPIRED: Token is expired");
|
|
816
|
+
}
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
return await handleAuthError("AUTH_ERROR: An unexpected error occurred", true);
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
let appKey = null;
|
|
825
|
+
/**
|
|
826
|
+
* Sets the main application encryption key.
|
|
827
|
+
* This key is expected to be used for encryption purposes within the application.
|
|
828
|
+
*
|
|
829
|
+
* @param {AppKeyConfig} config - An object containing the application encryption key.
|
|
830
|
+
* @param {string} config.key - The new application encryption key.
|
|
831
|
+
*
|
|
832
|
+
* @returns {void} Does not return anything, but updates the application key.
|
|
833
|
+
* @throws {Error} If the provided key is null, undefined, or an empty string.
|
|
834
|
+
*/
|
|
835
|
+
function configAppKey(config) {
|
|
836
|
+
if (!config || !config.appKey || config.appKey.trim() === "") {
|
|
837
|
+
throw new Error("The application encryption key cannot be null or empty.");
|
|
838
|
+
}
|
|
839
|
+
appKey = config.appKey;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Retrieves the current application encryption key.
|
|
843
|
+
* Throws an error if the application key has not been configured.
|
|
844
|
+
*
|
|
845
|
+
* @returns {string} The configured application encryption key.
|
|
846
|
+
* @throws {Error} If the application encryption key has not been set.
|
|
847
|
+
*/
|
|
848
|
+
function getAppKey() {
|
|
849
|
+
if (appKey === null) {
|
|
850
|
+
throw new Error("The application encryption key has not been configured. Please call 'configAppKey()' before attempting to access it.");
|
|
851
|
+
}
|
|
852
|
+
return appKey;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const SESSION_KEY = "session_config_";
|
|
856
|
+
const internalSessionState = {
|
|
857
|
+
sessionId: v4(),
|
|
858
|
+
persistencePreference: "session",
|
|
859
|
+
};
|
|
860
|
+
let _sessionConfig = Object.freeze({
|
|
861
|
+
SESSION_ID: internalSessionState.sessionId,
|
|
862
|
+
PERSISTENCE: internalSessionState.persistencePreference,
|
|
863
|
+
});
|
|
864
|
+
/**
|
|
865
|
+
* Updates the immutable `_sessionConfig` object with the current state of `internalSessionState`
|
|
866
|
+
* and freezes it.
|
|
867
|
+
*/
|
|
868
|
+
function updateSessionConfig() {
|
|
869
|
+
_sessionConfig = Object.freeze({
|
|
870
|
+
SESSION_ID: internalSessionState.sessionId,
|
|
871
|
+
PERSISTENCE: internalSessionState.persistencePreference,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Saves the current session configuration to local or session storage.
|
|
876
|
+
* @returns {Promise<void>} A promise that resolves when the session configuration has been saved.
|
|
877
|
+
*/
|
|
878
|
+
async function saveSessionConfig() {
|
|
879
|
+
const location = internalSessionState.persistencePreference;
|
|
880
|
+
try {
|
|
881
|
+
await storeEncryptedItem(SESSION_KEY, JSON.stringify(_sessionConfig), getAppKey(), location);
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
console.error("Error saving session configuration to storage:", error);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Attempts to load the configuration from storage.
|
|
889
|
+
* If it fails or is not found, `internalSessionState` will retain its current values (initial or last configured).
|
|
890
|
+
* Then, it updates `_sessionConfig`.
|
|
891
|
+
*
|
|
892
|
+
* @returns {Promise<void>} A promise that resolves when the state has been loaded and updated.
|
|
893
|
+
*/
|
|
894
|
+
async function loadSessionConfig() {
|
|
895
|
+
const location = internalSessionState.persistencePreference;
|
|
896
|
+
try {
|
|
897
|
+
const storedConfig = await getDecryptedItem(SESSION_KEY, getAppKey(), location);
|
|
898
|
+
if (storedConfig) {
|
|
899
|
+
const parsedConfig = JSON.parse(storedConfig);
|
|
900
|
+
internalSessionState.sessionId = parsedConfig.SESSION_ID;
|
|
901
|
+
internalSessionState.persistencePreference = parsedConfig.PERSISTENCE;
|
|
902
|
+
updateSessionConfig();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
console.warn(`Error loading or parsing from storage`, error);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Configures the session identifier and/or data persistence preference
|
|
911
|
+
* for the active browser session.
|
|
912
|
+
*
|
|
913
|
+
* This function is asynchronous because it will always attempt to load the current configuration
|
|
914
|
+
* before applying changes and then saving them.
|
|
915
|
+
*
|
|
916
|
+
* @param {SessionConfigObject} config - An object containing the unique session identifier and/or
|
|
917
|
+
* the persistence preference.
|
|
918
|
+
* @returns {Promise<void>} A promise that resolves when the session has been configured and saved.
|
|
919
|
+
*/
|
|
920
|
+
async function configSession(config) {
|
|
921
|
+
if (config.sessionId) {
|
|
922
|
+
internalSessionState.sessionId = config.sessionId;
|
|
923
|
+
}
|
|
924
|
+
if (config.persistencePreference) {
|
|
925
|
+
internalSessionState.persistencePreference = config.persistencePreference;
|
|
926
|
+
}
|
|
927
|
+
updateSessionConfig();
|
|
928
|
+
await saveSessionConfig();
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Retrieves the current session identifier.
|
|
932
|
+
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
933
|
+
*
|
|
934
|
+
* @returns {Promise<string>} A promise that resolves with the unique session identifier.
|
|
935
|
+
*/
|
|
936
|
+
async function getSessionId() {
|
|
937
|
+
await loadSessionConfig();
|
|
938
|
+
return _sessionConfig.SESSION_ID;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Retrieves the current data persistence preference.
|
|
942
|
+
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
943
|
+
*
|
|
944
|
+
* @returns {Promise<SessionPreference>} A promise that resolves with the configured persistence preference ('local' or 'session').
|
|
945
|
+
*/
|
|
946
|
+
async function getSessionPersistence() {
|
|
947
|
+
await loadSessionConfig();
|
|
948
|
+
return _sessionConfig.PERSISTENCE;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Retrieves the complete session configuration.
|
|
952
|
+
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
953
|
+
*
|
|
954
|
+
* @returns {Promise<SessionConfig>} A promise that resolves with the session configuration object.
|
|
955
|
+
*/
|
|
956
|
+
async function getSessionConfig() {
|
|
957
|
+
await loadSessionConfig();
|
|
958
|
+
return _sessionConfig;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
let endpointsConfig = {
|
|
962
|
+
LOGIN: "/login",
|
|
963
|
+
REFRESH: "/refresh",
|
|
964
|
+
LOGOUT: "/logout",
|
|
965
|
+
};
|
|
966
|
+
/**
|
|
967
|
+
* Configures authentication endpoint URLs globally.
|
|
968
|
+
* This function freezes the object to prevent further modifications.
|
|
969
|
+
*
|
|
970
|
+
* @param {EndpointConfig} config - An object containing the authentication endpoint URLs.
|
|
971
|
+
* @param {string} config.loginEndpoint - URL of the login endpoint.
|
|
972
|
+
* @param {string} config.refreshEndpoint - URL of the refresh token endpoint.
|
|
973
|
+
* @param {string} config.logoutEndpoint - URL of the logout endpoint.
|
|
974
|
+
*
|
|
975
|
+
* @returns {void} Does not return anything but freezes the endpoint configuration object.
|
|
976
|
+
*/
|
|
977
|
+
function configEndpoints(config) {
|
|
978
|
+
endpointsConfig = Object.freeze({
|
|
979
|
+
LOGIN: config.loginEndpoint,
|
|
980
|
+
REFRESH: config.refreshEndpoint,
|
|
981
|
+
LOGOUT: config.logoutEndpoint,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Retrieves the configured authentication endpoint URLs.
|
|
986
|
+
*
|
|
987
|
+
* @returns {EndpointsConfig} An object containing the configured authentication endpoints.
|
|
988
|
+
* @property {string} LOGIN - URL of the login endpoint.
|
|
989
|
+
* @property {string} REFRESH - URL of the refresh token endpoint.
|
|
990
|
+
* @property {string} LOGOUT - URL of the logout endpoint.
|
|
991
|
+
*/
|
|
992
|
+
function getEndpointsConfig() {
|
|
993
|
+
return endpointsConfig;
|
|
603
994
|
}
|
|
604
995
|
|
|
605
996
|
/**
|
|
@@ -623,9 +1014,6 @@ class AxiosService {
|
|
|
623
1014
|
timeout: 300000,
|
|
624
1015
|
headers: {
|
|
625
1016
|
'Accept': 'application/json',
|
|
626
|
-
'Cache-Control': 'no-cache',
|
|
627
|
-
'Pragma': 'no-cache',
|
|
628
|
-
'Expires': '0',
|
|
629
1017
|
...headers,
|
|
630
1018
|
},
|
|
631
1019
|
withCredentials: false,
|
|
@@ -638,9 +1026,8 @@ class AxiosService {
|
|
|
638
1026
|
* Initializes request and response interceptors for the Axios instance.
|
|
639
1027
|
*/
|
|
640
1028
|
initializeInterceptors() {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const token = typeof window !== 'undefined' ? localStorage.getItem(ACCESS_TOKEN) : null;
|
|
1029
|
+
this.instance.interceptors.request.use(async (config) => {
|
|
1030
|
+
const token = await getAuthToken(getAppKey(), 'any');
|
|
644
1031
|
if (token && config.headers) {
|
|
645
1032
|
config.headers.Authorization = `Bearer ${token}`;
|
|
646
1033
|
}
|
|
@@ -657,19 +1044,15 @@ class AxiosService {
|
|
|
657
1044
|
return response;
|
|
658
1045
|
}, async (error) => {
|
|
659
1046
|
this.activeRequests--;
|
|
660
|
-
// Check for unauthorized access (401)
|
|
661
1047
|
if (axios.isAxiosError(error) && error.response?.status === 401 && !this.refreshTokenInProgress) {
|
|
662
|
-
const refreshToken =
|
|
1048
|
+
const refreshToken = await getAuthRefreshToken(getAppKey(), "any");
|
|
663
1049
|
if (refreshToken) {
|
|
664
1050
|
this.refreshTokenInProgress = true;
|
|
665
1051
|
try {
|
|
666
|
-
// Attempt to refresh the token
|
|
667
1052
|
const refreshResponse = await axios.post(this.refreshTokenUrl, { refreshToken });
|
|
668
1053
|
const { token: newAccessToken } = refreshResponse.data;
|
|
669
1054
|
if (newAccessToken) {
|
|
670
|
-
|
|
671
|
-
localStorage.setItem(ACCESS_TOKEN, newAccessToken);
|
|
672
|
-
// Retry the original request with the new token
|
|
1055
|
+
storeAuthToken(newAccessToken, getAppKey(), await getSessionPersistence());
|
|
673
1056
|
if (error.config && error.config.headers) {
|
|
674
1057
|
error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
|
|
675
1058
|
}
|
|
@@ -682,8 +1065,7 @@ class AxiosService {
|
|
|
682
1065
|
catch (refreshError) {
|
|
683
1066
|
handleError(refreshError, false);
|
|
684
1067
|
this.refreshTokenInProgress = false;
|
|
685
|
-
|
|
686
|
-
localStorage.removeItem(REFRESH_TOKEN);
|
|
1068
|
+
cleanCredentials('any');
|
|
687
1069
|
}
|
|
688
1070
|
}
|
|
689
1071
|
}
|
|
@@ -695,354 +1077,75 @@ class AxiosService {
|
|
|
695
1077
|
* Returns the number of active requests.
|
|
696
1078
|
*/
|
|
697
1079
|
getActiveRequests() {
|
|
698
|
-
return this.activeRequests;
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* Returns the Axios instance with the configured settings and interceptors.
|
|
702
|
-
* @returns {AxiosInstance} The configured Axios instance.
|
|
703
|
-
*/
|
|
704
|
-
getAxiosInstance() {
|
|
705
|
-
return this.instance;
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Cancels all ongoing requests.
|
|
709
|
-
*/
|
|
710
|
-
cancelAllRequests() {
|
|
711
|
-
this.cancelTokenSource.cancel('Operation canceled by the user.');
|
|
712
|
-
this.cancelTokenSource = axios.CancelToken.source();
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Sets a new header for the Axios instance.
|
|
716
|
-
* @param {string} key - The header key.
|
|
717
|
-
* @param {string} value - The header value.
|
|
718
|
-
*/
|
|
719
|
-
setHeader(key, value) {
|
|
720
|
-
this.instance.defaults.headers.common[key] = value;
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Removes a header from the Axios instance.
|
|
724
|
-
* @param {string} key - The header key to remove.
|
|
725
|
-
*/
|
|
726
|
-
removeHeader(key) {
|
|
727
|
-
delete this.instance.defaults.headers.common[key];
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
let axiosInstance;
|
|
732
|
-
/**
|
|
733
|
-
* Configures the global Axios instance with a base URL.
|
|
734
|
-
*
|
|
735
|
-
* @param {AxiosConfig} config - An object containing the base URL for the Axios instance.
|
|
736
|
-
* @param {string} config.baseURL - The base URL for the Axios instance.
|
|
737
|
-
*
|
|
738
|
-
* @returns {void}
|
|
739
|
-
*/
|
|
740
|
-
const configAxios = (config) => {
|
|
741
|
-
axiosInstance = new AxiosService(config.baseURL);
|
|
742
|
-
};
|
|
743
|
-
/**
|
|
744
|
-
* Retrieves the configured Axios instance.
|
|
745
|
-
*
|
|
746
|
-
* @returns {AxiosService} The configured Axios instance.
|
|
747
|
-
* @throws Will throw an error if the Axios instance is not configured.
|
|
748
|
-
*/
|
|
749
|
-
const getAxiosInstance = () => {
|
|
750
|
-
if (!axiosInstance) {
|
|
751
|
-
throw new Error("Axios instance not configured. Call configAxios first.");
|
|
752
|
-
}
|
|
753
|
-
return axiosInstance.getAxiosInstance();
|
|
754
|
-
};
|
|
755
|
-
/**
|
|
756
|
-
* Creates a new AxiosService instance with custom headers.
|
|
757
|
-
*
|
|
758
|
-
* @param {CustomAxiosConfig} config - An object containing the base URL and optional custom headers.
|
|
759
|
-
* @param {string} config.baseURL - The base URL for the Axios instance.
|
|
760
|
-
* @param {Record<string, string>} [config.headers] - Custom headers to set (optional).
|
|
761
|
-
*
|
|
762
|
-
* @returns {AxiosService} The new AxiosService instance.
|
|
763
|
-
*/
|
|
764
|
-
const createCustomAxiosInstance = (config) => {
|
|
765
|
-
return new AxiosService(config.baseURL, config.headers).getAxiosInstance();
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Converts an ArrayBuffer or Uint8Array to a hexadecimal string.
|
|
770
|
-
* @param buffer The ArrayBuffer or Uint8Array to convert.
|
|
771
|
-
* @returns The hexadecimal string.
|
|
772
|
-
*/
|
|
773
|
-
function ab2hex(buffer) {
|
|
774
|
-
return Array.from(new Uint8Array(buffer))
|
|
775
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
776
|
-
.join("");
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Converts a hexadecimal string to a Uint8Array.
|
|
780
|
-
* @param hex The hexadecimal string to convert.
|
|
781
|
-
* @returns The Uint8Array.
|
|
782
|
-
* @throws {TypeError} If the input is not a string.
|
|
783
|
-
* @throws {Error} If the hexadecimal string format is invalid or has an odd length.
|
|
784
|
-
*/
|
|
785
|
-
function hex2ab(hex) {
|
|
786
|
-
if (typeof hex !== "string") {
|
|
787
|
-
throw new TypeError("Input must be a string.");
|
|
788
|
-
}
|
|
789
|
-
if (hex.length === 0) {
|
|
790
|
-
return new Uint8Array();
|
|
791
|
-
}
|
|
792
|
-
if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
|
|
793
|
-
throw new Error("Invalid hexadecimal string format or odd length.");
|
|
794
|
-
}
|
|
795
|
-
const array = new Uint8Array(hex.length / 2);
|
|
796
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
797
|
-
array[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
798
|
-
}
|
|
799
|
-
return array;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Derives an encryption key from a secret key.
|
|
803
|
-
* @param secretKey The secret key in plain text.
|
|
804
|
-
* @returns A promise that resolves with the derived CryptoKey.
|
|
805
|
-
* @throws {Error} If the secretKey is null or empty.
|
|
806
|
-
*/
|
|
807
|
-
async function importKey(secretKey) {
|
|
808
|
-
if (!secretKey) {
|
|
809
|
-
throw new Error("Secret key cannot be null or empty.");
|
|
810
|
-
}
|
|
811
|
-
const keyMaterial = new TextEncoder().encode(secretKey);
|
|
812
|
-
const digest = await crypto.subtle.digest("SHA-256", keyMaterial);
|
|
813
|
-
return crypto.subtle.importKey("raw", digest, { name: "AES-CBC", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Encrypts a value with the provided secret key.
|
|
817
|
-
* @param value The value to encrypt.
|
|
818
|
-
* @param secretKey The secret key for encryption.
|
|
819
|
-
* @returns A promise that resolves with the IV (hex) + ciphertext (hex) string.
|
|
820
|
-
* @throws {Error} If the secretKey is null or empty (via importKey).
|
|
821
|
-
*/
|
|
822
|
-
async function encrypt(value, secretKey) {
|
|
823
|
-
const key = await importKey(secretKey);
|
|
824
|
-
const iv = crypto.getRandomValues(new Uint8Array(16));
|
|
825
|
-
const encodedValue = new TextEncoder().encode(value);
|
|
826
|
-
const ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, key, encodedValue);
|
|
827
|
-
return ab2hex(iv) + ab2hex(new Uint8Array(ciphertext));
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Decrypts an encrypted value.
|
|
831
|
-
* @param encryptedValue The encrypted string (IV_hex + ciphertext_hex).
|
|
832
|
-
* @param secretKey The secret key for decryption.
|
|
833
|
-
* @returns A promise that resolves with the decrypted value.
|
|
834
|
-
* @throws {Error} If encryptedValue is null or empty, too short,
|
|
835
|
-
* or if the IV/ciphertext have incorrect lengths after conversion.
|
|
836
|
-
* @throws {Error} If the secretKey is null or empty (via importKey).
|
|
837
|
-
* @throws {TypeError} If hex2ab receives an invalid input type.
|
|
838
|
-
*/
|
|
839
|
-
async function decrypt(encryptedValue, secretKey) {
|
|
840
|
-
if (!encryptedValue) {
|
|
841
|
-
throw new Error("Encrypted value cannot be null or empty.");
|
|
842
|
-
}
|
|
843
|
-
// For AES-CBC, the IV is ALWAYS 16 bytes.
|
|
844
|
-
// 16 bytes * 2 hex characters/byte = 32 hex characters for the IV.
|
|
845
|
-
if (encryptedValue.length < 32) {
|
|
846
|
-
throw new Error("Encrypted value is too short. Expected at least 32 hexadecimal characters for the IV.");
|
|
847
|
-
}
|
|
848
|
-
const key = await importKey(secretKey);
|
|
849
|
-
const ivHex = encryptedValue.substring(0, 32);
|
|
850
|
-
const ciphertextHex = encryptedValue.substring(32);
|
|
851
|
-
const iv = hex2ab(ivHex);
|
|
852
|
-
const ciphertext = hex2ab(ciphertextHex);
|
|
853
|
-
if (iv.byteLength !== 16) {
|
|
854
|
-
throw new Error(`Converted IV has incorrect length: ${iv.byteLength} bytes. Expected 16 bytes.`);
|
|
855
|
-
}
|
|
856
|
-
if (ciphertext.byteLength === 0) {
|
|
857
|
-
throw new Error("Ciphertext is empty. No data to decrypt.");
|
|
858
|
-
}
|
|
859
|
-
const decryptedBuffer = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, ciphertext);
|
|
860
|
-
return new TextDecoder().decode(decryptedBuffer);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Encrypts and stores an item in local or session storage.
|
|
865
|
-
* Assumes the `window` environment is available.
|
|
866
|
-
* @param key The key under which to store the value.
|
|
867
|
-
* @param value The value to encrypt and store.
|
|
868
|
-
* @param secretKey The secret key for encryption.
|
|
869
|
-
* @param location Determines where the item is stored: 'local' for localStorage, 'session' for sessionStorage.
|
|
870
|
-
* @returns A promise that resolves when the item is stored. Throws an error if it fails.
|
|
871
|
-
*/
|
|
872
|
-
async function storeEncryptedItem(key, value, secretKey, location) {
|
|
873
|
-
if (typeof window === "undefined") {
|
|
874
|
-
throw new Error("Cannot access storage: window is not defined.");
|
|
875
|
-
}
|
|
876
|
-
const storage = location === "local" ? window.localStorage : window.sessionStorage;
|
|
877
|
-
const encryptedValue = await encrypt(value, secretKey);
|
|
878
|
-
storage.setItem(key, encryptedValue);
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Retrieves and decrypts a value from local or session storage.
|
|
882
|
-
* Assumes the `window` environment is available.
|
|
883
|
-
* @param key The key of the item to retrieve.
|
|
884
|
-
* @param secretKey The secret key for decryption.
|
|
885
|
-
* @param location Specifies where to search for the item: 'local' for localStorage, 'session' for sessionStorage, or 'any' to check both (session first).
|
|
886
|
-
* @returns A promise that resolves with the decrypted value or null if not found or decryption fails.
|
|
887
|
-
*/
|
|
888
|
-
async function getDecryptedItem(key, secretKey, location) {
|
|
889
|
-
if (typeof window === "undefined") {
|
|
890
|
-
return null;
|
|
891
|
-
}
|
|
892
|
-
let encryptedData = null;
|
|
893
|
-
if (location === "session" || location === "any") {
|
|
894
|
-
encryptedData = window.sessionStorage.getItem(key);
|
|
895
|
-
}
|
|
896
|
-
if (!encryptedData && (location === "local" || location === "any")) {
|
|
897
|
-
encryptedData = window.localStorage.getItem(key);
|
|
898
|
-
}
|
|
899
|
-
if (!encryptedData) {
|
|
900
|
-
return null;
|
|
901
|
-
}
|
|
902
|
-
try {
|
|
903
|
-
return await decrypt(encryptedData, secretKey);
|
|
904
|
-
}
|
|
905
|
-
catch (error) {
|
|
906
|
-
return null;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
let appKey = null;
|
|
911
|
-
/**
|
|
912
|
-
* Sets the main application encryption key.
|
|
913
|
-
* This key is expected to be used for encryption purposes within the application.
|
|
914
|
-
*
|
|
915
|
-
* @param {AppKeyConfig} config - An object containing the application encryption key.
|
|
916
|
-
* @param {string} config.key - The new application encryption key.
|
|
917
|
-
*
|
|
918
|
-
* @returns {void} Does not return anything, but updates the application key.
|
|
919
|
-
* @throws {Error} If the provided key is null, undefined, or an empty string.
|
|
920
|
-
*/
|
|
921
|
-
function configAppKey(config) {
|
|
922
|
-
if (!config || !config.appKey || config.appKey.trim() === "") {
|
|
923
|
-
throw new Error("The application encryption key cannot be null or empty.");
|
|
924
|
-
}
|
|
925
|
-
appKey = config.appKey;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Retrieves the current application encryption key.
|
|
929
|
-
* Throws an error if the application key has not been configured.
|
|
930
|
-
*
|
|
931
|
-
* @returns {string} The configured application encryption key.
|
|
932
|
-
* @throws {Error} If the application encryption key has not been set.
|
|
933
|
-
*/
|
|
934
|
-
function getAppKey() {
|
|
935
|
-
if (appKey === null) {
|
|
936
|
-
throw new Error("The application encryption key has not been configured. Please call 'configAppKey()' before attempting to access it.");
|
|
1080
|
+
return this.activeRequests;
|
|
937
1081
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
persistencePreference: "session",
|
|
945
|
-
};
|
|
946
|
-
let _sessionConfig = Object.freeze({
|
|
947
|
-
SESSION_ID: internalSessionState.sessionId,
|
|
948
|
-
PERSISTENCE: internalSessionState.persistencePreference,
|
|
949
|
-
});
|
|
950
|
-
/**
|
|
951
|
-
* Updates the immutable `_sessionConfig` object with the current state of `internalSessionState`
|
|
952
|
-
* and freezes it.
|
|
953
|
-
*/
|
|
954
|
-
function updateSessionConfig() {
|
|
955
|
-
_sessionConfig = Object.freeze({
|
|
956
|
-
SESSION_ID: internalSessionState.sessionId,
|
|
957
|
-
PERSISTENCE: internalSessionState.persistencePreference,
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Saves the current session configuration to local or session storage.
|
|
962
|
-
* @returns {Promise<void>} A promise that resolves when the session configuration has been saved.
|
|
963
|
-
*/
|
|
964
|
-
async function saveSessionConfig() {
|
|
965
|
-
const location = internalSessionState.persistencePreference;
|
|
966
|
-
try {
|
|
967
|
-
await storeEncryptedItem(SESSION_KEY, JSON.stringify(_sessionConfig), getAppKey(), location);
|
|
1082
|
+
/**
|
|
1083
|
+
* Returns the Axios instance with the configured settings and interceptors.
|
|
1084
|
+
* @returns {AxiosInstance} The configured Axios instance.
|
|
1085
|
+
*/
|
|
1086
|
+
getAxiosInstance() {
|
|
1087
|
+
return this.instance;
|
|
968
1088
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1089
|
+
/**
|
|
1090
|
+
* Cancels all ongoing requests.
|
|
1091
|
+
*/
|
|
1092
|
+
cancelAllRequests() {
|
|
1093
|
+
this.cancelTokenSource.cancel('Operation canceled by the user.');
|
|
1094
|
+
this.cancelTokenSource = axios.CancelToken.source();
|
|
971
1095
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
*/
|
|
980
|
-
async function loadSessionConfig() {
|
|
981
|
-
const location = internalSessionState.persistencePreference;
|
|
982
|
-
try {
|
|
983
|
-
const storedConfig = await getDecryptedItem(SESSION_KEY, getAppKey(), location);
|
|
984
|
-
if (storedConfig) {
|
|
985
|
-
const parsedConfig = JSON.parse(storedConfig);
|
|
986
|
-
internalSessionState.sessionId = parsedConfig.SESSION_ID;
|
|
987
|
-
internalSessionState.persistencePreference = parsedConfig.PERSISTENCE;
|
|
988
|
-
updateSessionConfig();
|
|
989
|
-
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Sets a new header for the Axios instance.
|
|
1098
|
+
* @param {string} key - The header key.
|
|
1099
|
+
* @param {string} value - The header value.
|
|
1100
|
+
*/
|
|
1101
|
+
setHeader(key, value) {
|
|
1102
|
+
this.instance.defaults.headers.common[key] = value;
|
|
990
1103
|
}
|
|
991
|
-
|
|
992
|
-
|
|
1104
|
+
/**
|
|
1105
|
+
* Removes a header from the Axios instance.
|
|
1106
|
+
* @param {string} key - The header key to remove.
|
|
1107
|
+
*/
|
|
1108
|
+
removeHeader(key) {
|
|
1109
|
+
delete this.instance.defaults.headers.common[key];
|
|
993
1110
|
}
|
|
994
1111
|
}
|
|
1112
|
+
|
|
1113
|
+
let axiosInstance;
|
|
995
1114
|
/**
|
|
996
|
-
* Configures the
|
|
997
|
-
* for the active browser session.
|
|
1115
|
+
* Configures the global Axios instance with a base URL.
|
|
998
1116
|
*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1117
|
+
* @param {AxiosConfig} config - An object containing the base URL for the Axios instance.
|
|
1118
|
+
* @param {string} config.baseURL - The base URL for the Axios instance.
|
|
1001
1119
|
*
|
|
1002
|
-
* @
|
|
1003
|
-
* the persistence preference.
|
|
1004
|
-
* @returns {Promise<void>} A promise that resolves when the session has been configured and saved.
|
|
1120
|
+
* @returns {void}
|
|
1005
1121
|
*/
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
if (config.persistencePreference) {
|
|
1011
|
-
internalSessionState.persistencePreference = config.persistencePreference;
|
|
1012
|
-
}
|
|
1013
|
-
updateSessionConfig();
|
|
1014
|
-
await saveSessionConfig();
|
|
1015
|
-
}
|
|
1122
|
+
const configAxios = (config) => {
|
|
1123
|
+
axiosInstance = new AxiosService(config.baseURL);
|
|
1124
|
+
};
|
|
1016
1125
|
/**
|
|
1017
|
-
* Retrieves the
|
|
1018
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1126
|
+
* Retrieves the configured Axios instance.
|
|
1019
1127
|
*
|
|
1020
|
-
* @returns {
|
|
1128
|
+
* @returns {AxiosService} The configured Axios instance.
|
|
1129
|
+
* @throws Will throw an error if the Axios instance is not configured.
|
|
1021
1130
|
*/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1131
|
+
const getAxiosInstance = () => {
|
|
1132
|
+
if (!axiosInstance) {
|
|
1133
|
+
throw new Error("Axios instance not configured. Call configAxios first.");
|
|
1134
|
+
}
|
|
1135
|
+
return axiosInstance.getAxiosInstance();
|
|
1136
|
+
};
|
|
1026
1137
|
/**
|
|
1027
|
-
*
|
|
1028
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1138
|
+
* Creates a new AxiosService instance with custom headers.
|
|
1029
1139
|
*
|
|
1030
|
-
* @
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
await loadSessionConfig();
|
|
1034
|
-
return _sessionConfig.PERSISTENCE;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Retrieves the complete session configuration.
|
|
1038
|
-
* Always attempts to load the configuration from storage. If it fails, it uses the internal state.
|
|
1140
|
+
* @param {CustomAxiosConfig} config - An object containing the base URL and optional custom headers.
|
|
1141
|
+
* @param {string} config.baseURL - The base URL for the Axios instance.
|
|
1142
|
+
* @param {Record<string, string>} [config.headers] - Custom headers to set (optional).
|
|
1039
1143
|
*
|
|
1040
|
-
* @returns {
|
|
1144
|
+
* @returns {AxiosService} The new AxiosService instance.
|
|
1041
1145
|
*/
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1146
|
+
const createCustomAxiosInstance = (config) => {
|
|
1147
|
+
return new AxiosService(config.baseURL, config.headers).getAxiosInstance();
|
|
1148
|
+
};
|
|
1046
1149
|
|
|
1047
1150
|
/**
|
|
1048
1151
|
* Creates and downloads a file from Blob data.
|
|
@@ -2706,118 +2809,6 @@ function isValidHexNumber(hex) {
|
|
|
2706
2809
|
// console.log(isValidHexNumber('1A3F')); // true
|
|
2707
2810
|
// console.log(isValidHexNumber('GHIJ')); // false
|
|
2708
2811
|
|
|
2709
|
-
/**
|
|
2710
|
-
* Clears all stored authentication data (access and refresh tokens)
|
|
2711
|
-
* from either sessionStorage, localStorage, or both based on the provided location preference.
|
|
2712
|
-
*
|
|
2713
|
-
* @param {LocationPreference} location - The storage preference ('local' for localStorage, 'session' for sessionStorage, 'any' for both).
|
|
2714
|
-
* @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
|
|
2715
|
-
*/
|
|
2716
|
-
const cleanCredentials = async (location) => {
|
|
2717
|
-
const tokensConfig = getTokenConfig();
|
|
2718
|
-
Object.keys(tokensConfig).forEach((key) => {
|
|
2719
|
-
const itemKey = tokensConfig[key];
|
|
2720
|
-
if (location === "local" || location === "any") {
|
|
2721
|
-
localStorage.removeItem(itemKey);
|
|
2722
|
-
}
|
|
2723
|
-
if (location === "session" || location === "any") {
|
|
2724
|
-
sessionStorage.removeItem(itemKey);
|
|
2725
|
-
}
|
|
2726
|
-
});
|
|
2727
|
-
};
|
|
2728
|
-
/**
|
|
2729
|
-
* Retrieves the authentication token (access token) from storage, decrypting it
|
|
2730
|
-
* using the provided secret key and based on the specified session preference.
|
|
2731
|
-
*
|
|
2732
|
-
* @param {string} secretKey - The secret key used for decryption.
|
|
2733
|
-
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
2734
|
-
* @returns {Promise<string | null>} A promise that resolves with the decrypted access token, or null if not found.
|
|
2735
|
-
*/
|
|
2736
|
-
const getAuthToken = async (secretKey, location) => {
|
|
2737
|
-
const tokensConfig = getTokenConfig();
|
|
2738
|
-
return await getDecryptedItem(tokensConfig.ACCESS_TOKEN, secretKey, location);
|
|
2739
|
-
};
|
|
2740
|
-
/**
|
|
2741
|
-
* Retrieves the authentication refresh token from storage, decrypting it
|
|
2742
|
-
* using the provided secret key and based on the specified session preference.
|
|
2743
|
-
*
|
|
2744
|
-
* @param {string} secretKey - The secret key used for decryption.
|
|
2745
|
-
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
2746
|
-
* @returns {Promise<string | null>} A promise that resolves with the decrypted refresh token, or null if not found.
|
|
2747
|
-
*/
|
|
2748
|
-
const getAuthRefreshToken = async (secretKey, location) => {
|
|
2749
|
-
const tokensConfig = getTokenConfig();
|
|
2750
|
-
return await getDecryptedItem(tokensConfig.REFRESH_TOKEN, secretKey, location);
|
|
2751
|
-
};
|
|
2752
|
-
/**
|
|
2753
|
-
* Stores the authentication token (access token) in storage after encrypting it,
|
|
2754
|
-
* based on the specified session preference.
|
|
2755
|
-
*
|
|
2756
|
-
* @param {string} token - The access token to store.
|
|
2757
|
-
* @param {string} secretKey - The secret key used for encryption.
|
|
2758
|
-
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
2759
|
-
* @returns {Promise<void>} A promise that resolves when the token is successfully stored.
|
|
2760
|
-
*/
|
|
2761
|
-
const storeAuthToken = async (token, secretKey, location) => {
|
|
2762
|
-
const tokensConfig = getTokenConfig();
|
|
2763
|
-
await storeEncryptedItem(tokensConfig.ACCESS_TOKEN, token, secretKey, location);
|
|
2764
|
-
};
|
|
2765
|
-
/**
|
|
2766
|
-
* Stores the authentication refresh token in storage after encrypting it,
|
|
2767
|
-
* based on the specified session preference.
|
|
2768
|
-
*
|
|
2769
|
-
* @param {string} token - The refresh token to store.
|
|
2770
|
-
* @param {string} secretKey - The secret key used for encryption.
|
|
2771
|
-
* @param {SessionPreference} preference - The storage preference ('local' for localStorage, 'session' for sessionStorage).
|
|
2772
|
-
* @returns {Promise<void>} A promise that resolves when the token is successfully stored.
|
|
2773
|
-
*/
|
|
2774
|
-
const storeAuthRefreshToken = async (token, secretKey, location) => {
|
|
2775
|
-
const tokensConfig = getTokenConfig();
|
|
2776
|
-
await storeEncryptedItem(tokensConfig.REFRESH_TOKEN, token, secretKey, location);
|
|
2777
|
-
};
|
|
2778
|
-
/**
|
|
2779
|
-
* Verifies the validity and expiration of the current authentication token.
|
|
2780
|
-
* If the token is missing, invalid, or expired, appropriate errors are thrown and credentials are cleaned.
|
|
2781
|
-
*
|
|
2782
|
-
* @returns {Promise<boolean>} True if the token is valid and unexpired.
|
|
2783
|
-
* @throws {Error} "TOKEN_MISSING" if no token is found, "TOKEN_EXPIRED" if the token has expired,
|
|
2784
|
-
* "TOKEN_INVALID" if the token format is invalid.
|
|
2785
|
-
*/
|
|
2786
|
-
const verifyAuth = async () => {
|
|
2787
|
-
const sessionPersistence = 'any';
|
|
2788
|
-
const handleAuthError = async (message, shouldClean = true) => {
|
|
2789
|
-
handleError(message, false);
|
|
2790
|
-
if (shouldClean) {
|
|
2791
|
-
await cleanCredentials(sessionPersistence);
|
|
2792
|
-
}
|
|
2793
|
-
return false;
|
|
2794
|
-
};
|
|
2795
|
-
try {
|
|
2796
|
-
const token = await getAuthToken(getAppKey(), sessionPersistence);
|
|
2797
|
-
if (!token) {
|
|
2798
|
-
return await handleAuthError("TOKEN_MISSING: No valid token found");
|
|
2799
|
-
}
|
|
2800
|
-
let decoded;
|
|
2801
|
-
try {
|
|
2802
|
-
decoded = jwtDecode(token);
|
|
2803
|
-
}
|
|
2804
|
-
catch (decodeError) {
|
|
2805
|
-
return await handleAuthError("TOKEN_INVALID: Invalid token format");
|
|
2806
|
-
}
|
|
2807
|
-
const currentTime = Date.now() / 1000;
|
|
2808
|
-
if (typeof decoded.exp !== "number") {
|
|
2809
|
-
return await handleAuthError("TOKEN_INVALID: Invalid expiration format");
|
|
2810
|
-
}
|
|
2811
|
-
if (decoded.exp <= currentTime) {
|
|
2812
|
-
return await handleAuthError("TOKEN_EXPIRED: Token is expired");
|
|
2813
|
-
}
|
|
2814
|
-
return true;
|
|
2815
|
-
}
|
|
2816
|
-
catch (error) {
|
|
2817
|
-
return await handleAuthError("AUTH_ERROR: An unexpected error occurred", true);
|
|
2818
|
-
}
|
|
2819
|
-
};
|
|
2820
|
-
|
|
2821
2812
|
class RestStd {
|
|
2822
2813
|
static resource;
|
|
2823
2814
|
static isFormData = false;
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@arex95/vue-core",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Opinionated Vue Core",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"exports": {
|
|
8
|
-
"import": "./dist/index.mjs"
|
|
9
|
-
},
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
|
-
"type": "module",
|
|
12
|
-
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "rollup -c",
|
|
17
|
-
"changelog": "conventional-changelog -p angular -o CHANGELOG.md -r 0",
|
|
18
|
-
"release": "npm version patch && npm run changelog && git add CHANGELOG.md package.json pnpm-lock.yaml && git commit -m \"chore(release): update changelog\" && git push && npm publish --access public",
|
|
19
|
-
"test": "vitest",
|
|
20
|
-
"test:watch": "vitest --watch",
|
|
21
|
-
"test:ui": "vitest --ui"
|
|
22
|
-
},
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "https://github.com/Arex95/npm-arex-core.git"
|
|
26
|
-
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"vue",
|
|
29
|
-
"composables",
|
|
30
|
-
"core",
|
|
31
|
-
"npm"
|
|
32
|
-
],
|
|
33
|
-
"author": "Arturo Rafael Serrano Girón",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"@tanstack/vue-query": ">=5.0.0",
|
|
37
|
-
"@vueuse/core": ">=12.8.2",
|
|
38
|
-
"axios": ">=1.6.0",
|
|
39
|
-
"jwt-decode": "^4.0.0",
|
|
40
|
-
"uuid": ">=11.1.0",
|
|
41
|
-
"vue": ">=3.0.0",
|
|
42
|
-
"vue-router": ">=4.5.0"
|
|
43
|
-
},
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"@eslint/js": "^9.23.0",
|
|
46
|
-
"@rollup/plugin-commonjs": "^28.0.3",
|
|
47
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
48
|
-
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
49
|
-
"@rollup/plugin-typescript": "^12.1.2",
|
|
50
|
-
"@types/crypto-js": "^4.2.2",
|
|
51
|
-
"@types/node": "^22.13.10",
|
|
52
|
-
"@vitest/spy": "^3.2.3",
|
|
53
|
-
"@vitest/ui": "^3.2.3",
|
|
54
|
-
"@vue/test-utils": "^2.4.6",
|
|
55
|
-
"conventional-changelog-cli": "^5.0.0",
|
|
56
|
-
"eslint": "^9.23.0",
|
|
57
|
-
"eslint-plugin-vue": "^10.0.0",
|
|
58
|
-
"globals": "^16.0.0",
|
|
59
|
-
"jsdom": "^26.1.0",
|
|
60
|
-
"rollup": "^4.35.0",
|
|
61
|
-
"ts-node": "^10.9.2",
|
|
62
|
-
"tslib": "^2.8.1",
|
|
63
|
-
"typescript": "^5.8.2",
|
|
64
|
-
"typescript-eslint": "^8.28.0",
|
|
65
|
-
"vitest": "^3.2.3"
|
|
66
|
-
},
|
|
67
|
-
"dependencies": {
|
|
68
|
-
"@tanstack/vue-query": ">=5.0.0",
|
|
69
|
-
"@vueuse/core": ">=12.8.2",
|
|
70
|
-
"axios": ">=1.6.0",
|
|
71
|
-
"jwt-decode": "^4.0.0",
|
|
72
|
-
"uuid": ">=11.1.0",
|
|
73
|
-
"vue": ">=3.0.0",
|
|
74
|
-
"vue-router": ">=4.5.0"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@arex95/vue-core",
|
|
3
|
+
"version": "1.1.36",
|
|
4
|
+
"description": "Opinionated Vue Core",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
"import": "./dist/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "rollup -c",
|
|
17
|
+
"changelog": "conventional-changelog -p angular -o CHANGELOG.md -r 0",
|
|
18
|
+
"release": "npm version patch && npm run changelog && git add CHANGELOG.md package.json pnpm-lock.yaml && git commit -m \"chore(release): update changelog\" && git push && npm publish --access public",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:watch": "vitest --watch",
|
|
21
|
+
"test:ui": "vitest --ui"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/Arex95/npm-arex-core.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"vue",
|
|
29
|
+
"composables",
|
|
30
|
+
"core",
|
|
31
|
+
"npm"
|
|
32
|
+
],
|
|
33
|
+
"author": "Arturo Rafael Serrano Girón",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@tanstack/vue-query": ">=5.0.0",
|
|
37
|
+
"@vueuse/core": ">=12.8.2",
|
|
38
|
+
"axios": ">=1.6.0",
|
|
39
|
+
"jwt-decode": "^4.0.0",
|
|
40
|
+
"uuid": ">=11.1.0",
|
|
41
|
+
"vue": ">=3.0.0",
|
|
42
|
+
"vue-router": ">=4.5.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.23.0",
|
|
46
|
+
"@rollup/plugin-commonjs": "^28.0.3",
|
|
47
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
48
|
+
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
49
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
50
|
+
"@types/crypto-js": "^4.2.2",
|
|
51
|
+
"@types/node": "^22.13.10",
|
|
52
|
+
"@vitest/spy": "^3.2.3",
|
|
53
|
+
"@vitest/ui": "^3.2.3",
|
|
54
|
+
"@vue/test-utils": "^2.4.6",
|
|
55
|
+
"conventional-changelog-cli": "^5.0.0",
|
|
56
|
+
"eslint": "^9.23.0",
|
|
57
|
+
"eslint-plugin-vue": "^10.0.0",
|
|
58
|
+
"globals": "^16.0.0",
|
|
59
|
+
"jsdom": "^26.1.0",
|
|
60
|
+
"rollup": "^4.35.0",
|
|
61
|
+
"ts-node": "^10.9.2",
|
|
62
|
+
"tslib": "^2.8.1",
|
|
63
|
+
"typescript": "^5.8.2",
|
|
64
|
+
"typescript-eslint": "^8.28.0",
|
|
65
|
+
"vitest": "^3.2.3"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@tanstack/vue-query": ">=5.0.0",
|
|
69
|
+
"@vueuse/core": ">=12.8.2",
|
|
70
|
+
"axios": ">=1.6.0",
|
|
71
|
+
"jwt-decode": "^4.0.0",
|
|
72
|
+
"uuid": ">=11.1.0",
|
|
73
|
+
"vue": ">=3.0.0",
|
|
74
|
+
"vue-router": ">=4.5.0"
|
|
75
|
+
}
|
|
76
|
+
}
|