@arex95/vue-core 1.1.15 → 1.1.19

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.
@@ -1,17 +1,57 @@
1
1
  /**
2
- * Provides authentication utilities.
3
- *
4
- * @param {string} [secretKey=getSecretKey()] - Encryption key.
5
- * @returns {Object} Auth composable methods and properties.
2
+ * Converts an ArrayBuffer to a hexadecimal string.
3
+ * @param {Uint8Array} buffer - The Uint8Array to convert.
4
+ * @returns {string} The hexadecimal string representation.
5
+ */
6
+ declare function ab2hex(buffer: Uint8Array): string;
7
+ /**
8
+ * Converts a hexadecimal string to a Uint8Array.
9
+ * @param {string} hex - The hexadecimal string to convert.
10
+ * @returns {Uint8Array} The Uint8Array representation.
11
+ */
12
+ declare function hex2ab(hex: string): Uint8Array;
13
+ /**
14
+ * Imports a secret key for cryptographic operations.
15
+ * @param {string} secretKey - The string secret key.
16
+ * @returns {Promise<CryptoKey>} A Promise that resolves to the CryptoKey.
17
+ */
18
+ declare function importKey(secretKey: string): Promise<CryptoKey>;
19
+ /**
20
+ * Interface for the expected structure of a successful login response from the API.
21
+ */
22
+ interface LoginResponse {
23
+ access_token: string;
24
+ refresh_token: string;
25
+ [key: string]: unknown;
26
+ }
27
+ /**
28
+ * Interface for the expected structure of a successful token refresh response from the API.
29
+ */
30
+ interface RefreshResponse {
31
+ access_token: string;
32
+ refresh_token: string;
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * A Vue composable that provides authentication utilities, enforcing asynchronous access to tokens.
37
+ * All token-related properties and status checks are awaitable functions.
38
+ * @param {string} [secretKey=getSecretKey()] - The encryption/decryption key. Defaults to a globally configured key.
39
+ * @returns {object} An object containing asynchronous authentication methods and properties.
6
40
  */
7
41
  export declare function useAuth(secretKey?: string): {
8
- isAuthenticated: import("vue").ComputedRef<boolean>;
9
- jwt: import("vue").Ref<string | null, string | null>;
10
- refresh_token: import("vue").Ref<string | null, string | null>;
11
- tokenExpiry: import("vue").ComputedRef<number | null>;
12
- login: (params: {} | undefined, isRememberMe: boolean) => Promise<import("axios").AxiosResponse<any, any>>;
13
- refresh: () => Promise<import("axios").AxiosResponse<any, any> | undefined>;
14
- logout: (params?: {}) => Promise<void>;
42
+ getJwt: () => Promise<string | null>;
43
+ getRefreshToken: () => Promise<string | null>;
44
+ isAuthenticated: () => Promise<boolean>;
45
+ getTokenExpiry: () => Promise<number | null>;
46
+ login: (params: object | undefined, isRememberMe: boolean) => Promise<LoginResponse>;
47
+ refresh: () => Promise<RefreshResponse>;
48
+ logout: (params?: object) => Promise<void>;
15
49
  cleanStorage: () => Promise<void>;
16
50
  verifyToken: () => Promise<void>;
51
+ ab2hex: typeof ab2hex;
52
+ hex2ab: typeof hex2ab;
53
+ importKey: typeof importKey;
54
+ encrypt: (value: string, secretKey: string) => Promise<string>;
55
+ decrypt: (value: string, secretKey: string) => Promise<string>;
17
56
  };
57
+ export {};
@@ -1,4 +1,12 @@
1
- export declare function useApiActivity(sessionTimeoutMin: number, checkIntervalSec: number): {
1
+ /**
2
+ * A Vue composable to monitor API activity and manage session timeouts.
3
+ * It tracks the last user activity and automatically logs out the user
4
+ * if no activity is detected within a specified timeout.
5
+ * @param {number} [sessionTimeoutMin=SESSION_TIMEOUT_MINUTES] - The time in minutes after which an inactive session will time out.
6
+ * @param {number} [checkIntervalSec=CHECK_INTERVAL_SECONDS] - The interval in seconds at which the session timeout is checked.
7
+ * @returns {object} An object containing methods to pause, resume, and manually update the activity timestamp.
8
+ */
9
+ export declare function useApiActivity(sessionTimeoutMin?: number, checkIntervalSec?: number): {
2
10
  pause: import("@vueuse/core").Fn;
3
11
  resume: import("@vueuse/core").Fn;
4
12
  updateTimestamp: () => void;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize, computedAsync } from '@vueuse/core';
1
+ import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
2
2
  import axios from 'axios';
3
3
  import { useRouter } from 'vue-router';
4
4
  import { useQuery } from '@tanstack/vue-query';
@@ -2989,14 +2989,33 @@ function useSorter(items, criteriaList, selectedCriteria) {
2989
2989
  }).value;
2990
2990
  }
2991
2991
 
2992
+ /**
2993
+ * Converts an ArrayBuffer to a hexadecimal string.
2994
+ * @param {Uint8Array} buffer - The Uint8Array to convert.
2995
+ * @returns {string} The hexadecimal string representation.
2996
+ */
2992
2997
  function ab2hex(buffer) {
2993
2998
  return Array.from(buffer)
2994
2999
  .map((b) => b.toString(16).padStart(2, "0"))
2995
3000
  .join("");
2996
3001
  }
3002
+ /**
3003
+ * Converts a hexadecimal string to a Uint8Array.
3004
+ * @param {string} hex - The hexadecimal string to convert.
3005
+ * @returns {Uint8Array} The Uint8Array representation.
3006
+ */
2997
3007
  function hex2ab(hex) {
2998
- return new Uint8Array(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
3008
+ const matches = hex.match(/.{1,2}/g);
3009
+ if (!matches) {
3010
+ return new Uint8Array([]);
3011
+ }
3012
+ return new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
2999
3013
  }
3014
+ /**
3015
+ * Imports a secret key for cryptographic operations.
3016
+ * @param {string} secretKey - The string secret key.
3017
+ * @returns {Promise<CryptoKey>} A Promise that resolves to the CryptoKey.
3018
+ */
3000
3019
  async function importKey(secretKey) {
3001
3020
  const encoder = new TextEncoder();
3002
3021
  const keyMaterial = await crypto.subtle.digest("SHA-256", encoder.encode(secretKey));
@@ -3005,7 +3024,6 @@ async function importKey(secretKey) {
3005
3024
  /**
3006
3025
  * Encrypts a value using AES-256 CBC encryption with Web Cryptography API.
3007
3026
  * The IV is generated randomly for each encryption and prepended to the ciphertext.
3008
- *
3009
3027
  * @param {string} value - The value to encrypt.
3010
3028
  * @param {string} secretKey - The encryption key string.
3011
3029
  * @returns {Promise<string>} A promise that resolves to the encrypted string (hex IV + hex ciphertext).
@@ -3022,7 +3040,6 @@ const encrypt = async (value, secretKey) => {
3022
3040
  /**
3023
3041
  * Decrypts an AES-256 CBC encrypted value using Web Cryptography API.
3024
3042
  * The IV is extracted from the beginning of the encrypted string.
3025
- *
3026
3043
  * @param {string} value - The encrypted string (hex IV + hex ciphertext).
3027
3044
  * @param {string} secretKey - The decryption key string.
3028
3045
  * @returns {Promise<string>} A promise that resolves to the decrypted string.
@@ -3036,13 +3053,13 @@ const decrypt = async (value, secretKey) => {
3036
3053
  };
3037
3054
  /**
3038
3055
  * Stores an encrypted value in sessionStorage or localStorage.
3039
- *
3040
3056
  * @param {string} key - Storage key.
3041
3057
  * @param {string} value - Value to store.
3042
3058
  * @param {string} secretKey - Encryption key.
3043
- * @param {boolean} isRememberMe - Whether to store in localStorage.
3059
+ * @param {boolean} isRememberMe - Whether to store in localStorage (true) or sessionStorage (false).
3044
3060
  * @param {number} [attempt=0] - Retry attempt count.
3045
- * @returns {Promise<void>}
3061
+ * @returns {Promise<void>} A Promise that resolves when the item is stored.
3062
+ * @throws {Error} If storage is not available after multiple attempts.
3046
3063
  */
3047
3064
  const storeEncryptedItem = async (key, value, secretKey, isRememberMe, attempt = 0) => {
3048
3065
  const storage = isRememberMe ? localStorage : sessionStorage;
@@ -3070,15 +3087,14 @@ const storeEncryptedItem = async (key, value, secretKey, isRememberMe, attempt =
3070
3087
  }
3071
3088
  };
3072
3089
  /**
3073
- * Retrieves and decrypts a stored value.
3074
- *
3090
+ * Retrieves and decrypts a stored value from sessionStorage or localStorage.
3075
3091
  * @param {string} key - Storage key.
3076
3092
  * @param {string} secretKey - Decryption key.
3077
- * @param {boolean} isRememberMe - Whether to retrieve from localStorage.
3078
- * @returns {Promise<string|null>} The decrypted value or null.
3093
+ * @param {boolean} isRememberMe - Whether to retrieve from localStorage (true) or sessionStorage (false).
3094
+ * @returns {Promise<string|null>} The decrypted value or null if not found or decryption fails.
3079
3095
  */
3080
3096
  async function getDecryptedValue(key, secretKey, isRememberMe) {
3081
- const storage = sessionStorage;
3097
+ const storage = isRememberMe ? localStorage : sessionStorage;
3082
3098
  try {
3083
3099
  const value = storage.getItem(key);
3084
3100
  if (!value)
@@ -3091,58 +3107,84 @@ async function getDecryptedValue(key, secretKey, isRememberMe) {
3091
3107
  }
3092
3108
  }
3093
3109
  /**
3094
- * Provides authentication utilities.
3095
- *
3096
- * @param {string} [secretKey=getSecretKey()] - Encryption key.
3097
- * @returns {Object} Auth composable methods and properties.
3110
+ * A Vue composable that provides authentication utilities, enforcing asynchronous access to tokens.
3111
+ * All token-related properties and status checks are awaitable functions.
3112
+ * @param {string} [secretKey=getSecretKey()] - The encryption/decryption key. Defaults to a globally configured key.
3113
+ * @returns {object} An object containing asynchronous authentication methods and properties.
3098
3114
  */
3099
3115
  function useAuth(secretKey = getSecretKey()) {
3100
3116
  const axiosInstance = getAxiosInstance();
3101
3117
  const tokensConfig = getTokenConfig();
3102
3118
  const endpointsConfig = getEndpointsConfig();
3103
- const config = {
3119
+ const authConfig = {
3104
3120
  endpoints: endpointsConfig,
3105
3121
  storageKeys: tokensConfig,
3106
3122
  };
3107
- const jwt = computedAsync(async () => await getDecryptedValue(config.storageKeys.ACCESS_TOKEN, secretKey), null);
3108
- const refresh_token = computedAsync(async () => await getDecryptedValue(config.storageKeys.REFRESH_TOKEN, secretKey), null);
3109
3123
  /**
3110
- * Computes token expiration timestamp.
3124
+ * Asynchronously retrieves and decrypts the Access Token (JWT) from storage.
3125
+ * This function should always be awaited.
3126
+ * @returns {Promise<string|null>} A promise that resolves to the decrypted JWT string or null if not found.
3127
+ */
3128
+ const getJwt = async () => {
3129
+ return await getDecryptedValue(authConfig.storageKeys.ACCESS_TOKEN, secretKey, false);
3130
+ };
3131
+ /**
3132
+ * Asynchronously retrieves and decrypts the Refresh Token from storage.
3133
+ * This function should always be awaited.
3134
+ * @returns {Promise<string|null>} A promise that resolves to the decrypted Refresh Token string or null if not found.
3111
3135
  */
3112
- const tokenExpiry = computed(() => {
3113
- if (!jwt.value)
3136
+ const getRefreshToken = async () => {
3137
+ return await getDecryptedValue(authConfig.storageKeys.REFRESH_TOKEN, secretKey, true);
3138
+ };
3139
+ /**
3140
+ * Asynchronously computes the expiration timestamp of the current Access Token.
3141
+ * Requires awaiting the JWT.
3142
+ * @returns {Promise<number|null>} A promise that resolves to the expiration timestamp in milliseconds (Unix epoch) or null if no valid token is found or parsing fails.
3143
+ */
3144
+ const getTokenExpiry = async () => {
3145
+ const token = await getJwt();
3146
+ if (!token)
3114
3147
  return null;
3115
3148
  try {
3116
- const decoded = jwtDecode(jwt.value);
3149
+ const decoded = jwtDecode(token);
3117
3150
  return decoded.exp ? decoded.exp * 1000 : null;
3118
3151
  }
3119
3152
  catch (error) {
3120
3153
  handleError(error, false);
3121
3154
  return null;
3122
3155
  }
3123
- });
3156
+ };
3124
3157
  /**
3125
- * Checks if the user is authenticated.
3158
+ * Asynchronously checks if the user is currently authenticated and if the Access Token is valid and not expired.
3159
+ * This function should always be awaited.
3160
+ * @returns {Promise<boolean>} A promise that resolves to true if authenticated and token is valid, false otherwise.
3126
3161
  */
3127
- const isAuthenticated = computed(() => {
3128
- if (!jwt.value || jwt.value.length === 0) {
3162
+ const isAuthenticated = async () => {
3163
+ const token = await getJwt();
3164
+ if (!token || token.length === 0) {
3129
3165
  return false;
3130
3166
  }
3131
- if (tokenExpiry.value === null) {
3167
+ const expiry = await getTokenExpiry();
3168
+ if (expiry === null) {
3132
3169
  return false;
3133
3170
  }
3134
- return tokenExpiry.value > Date.now();
3135
- });
3171
+ return expiry > Date.now();
3172
+ };
3136
3173
  /**
3137
- * Handles user login.
3174
+ * Handles user login by making an API request and storing the received tokens.
3175
+ * @param {object} [params={}] - The login credentials or payload.
3176
+ * @param {boolean} isRememberMe - Indicates whether the refresh token should be stored in localStorage.
3177
+ * @returns {Promise<LoginResponse>} A promise that resolves to the API response data on successful login.
3178
+ * @throws {Error} If the login request fails.
3138
3179
  */
3139
3180
  const login = async (params = {}, isRememberMe) => {
3140
3181
  try {
3141
- const response = await axiosInstance.post(config.endpoints.LOGIN, params);
3142
- await storeEncryptedItem(config.storageKeys.ACCESS_TOKEN, response.data.access_token, secretKey, isRememberMe);
3143
- await storeEncryptedItem(config.storageKeys.REFRESH_TOKEN, response.data.refresh_token, secretKey, isRememberMe);
3144
- await jwt.value;
3145
- return response;
3182
+ const response = await axiosInstance.post(authConfig.endpoints.LOGIN, params);
3183
+ const newAccessToken = response.data.access_token;
3184
+ const newRefreshToken = response.data.refresh_token;
3185
+ await storeEncryptedItem(authConfig.storageKeys.ACCESS_TOKEN, newAccessToken, secretKey, false);
3186
+ await storeEncryptedItem(authConfig.storageKeys.REFRESH_TOKEN, newRefreshToken, secretKey, isRememberMe);
3187
+ return response.data;
3146
3188
  }
3147
3189
  catch (error) {
3148
3190
  handleError(error, false);
@@ -3150,26 +3192,34 @@ function useAuth(secretKey = getSecretKey()) {
3150
3192
  }
3151
3193
  };
3152
3194
  /**
3153
- * Refreshes authentication token.
3195
+ * Refreshes the authentication token by making an API request and updating stored tokens.
3196
+ * @returns {Promise<RefreshResponse>} A promise that resolves to the API response data on successful refresh.
3197
+ * @throws {Error} If the refresh request fails, leading to logout.
3154
3198
  */
3155
3199
  const refresh = async () => {
3156
3200
  try {
3157
- const response = await axiosInstance.post(config.endpoints.REFRESH, {});
3158
- await storeEncryptedItem(config.storageKeys.ACCESS_TOKEN, response.data.token, secretKey, false);
3159
- await storeEncryptedItem(config.storageKeys.REFRESH_TOKEN, response.data.refresh_token, secretKey, false);
3160
- return response;
3201
+ const response = await axiosInstance.post(authConfig.endpoints.REFRESH, {});
3202
+ const newAccessToken = response.data.access_token;
3203
+ const newRefreshToken = response.data.refresh_token;
3204
+ await storeEncryptedItem(authConfig.storageKeys.ACCESS_TOKEN, newAccessToken, secretKey, false);
3205
+ await storeEncryptedItem(authConfig.storageKeys.REFRESH_TOKEN, newRefreshToken, secretKey, true);
3206
+ return response.data;
3161
3207
  }
3162
3208
  catch (error) {
3163
3209
  handleError(error, false);
3164
3210
  await logout();
3211
+ throw error;
3165
3212
  }
3166
3213
  };
3167
3214
  /**
3168
- * Logs out the user.
3215
+ * Handles user logout by making an API request and clearing stored authentication data.
3216
+ * Reloads the page after clearing storage.
3217
+ * @param {object} [params={}] - Optional logout payload.
3218
+ * @returns {Promise<void>} A promise that resolves when the logout process is complete.
3169
3219
  */
3170
3220
  const logout = async (params = {}) => {
3171
3221
  try {
3172
- await axiosInstance.post(config.endpoints.LOGOUT, params);
3222
+ await axiosInstance.post(authConfig.endpoints.LOGOUT, params);
3173
3223
  }
3174
3224
  catch (error) {
3175
3225
  handleError(error, false);
@@ -3180,25 +3230,31 @@ function useAuth(secretKey = getSecretKey()) {
3180
3230
  }
3181
3231
  };
3182
3232
  /**
3183
- * Clears stored authentication data.
3233
+ * Clears all stored authentication data (access and refresh tokens) from both sessionStorage and localStorage.
3234
+ * @returns {Promise<void>} A promise that resolves when all relevant storage items are removed.
3184
3235
  */
3185
3236
  const cleanStorage = async () => {
3186
- Object.keys(config.storageKeys).forEach((key) => {
3187
- sessionStorage.removeItem(config.storageKeys[key]);
3188
- localStorage.removeItem(config.storageKeys[key]);
3237
+ Object.keys(authConfig.storageKeys).forEach((key) => {
3238
+ sessionStorage.removeItem(authConfig.storageKeys[key]);
3239
+ localStorage.removeItem(authConfig.storageKeys[key]);
3189
3240
  });
3190
3241
  };
3191
3242
  /**
3192
- * Verifies token validity.
3243
+ * Verifies the validity of the current Access Token. If the token is missing, invalid, or expired,
3244
+ * it handles the error appropriately (e.g., attempts refresh, clears storage, redirects).
3245
+ * This function should always be awaited.
3246
+ * @returns {Promise<void>} A promise that resolves if the token is valid, or rejects with an error.
3247
+ * @throws {Error} If the token is missing, expired, or invalid.
3193
3248
  */
3194
3249
  const verifyToken = async () => {
3195
- if (!jwt.value) {
3250
+ const token = await getJwt();
3251
+ if (!token) {
3196
3252
  handleError("TOKEN_MISSING: No valid token found", true, "/auth-error", "query");
3197
3253
  await cleanStorage();
3198
3254
  throw new Error("TOKEN_MISSING: No valid token found");
3199
3255
  }
3200
3256
  try {
3201
- const decoded = jwtDecode(jwt.value);
3257
+ const decoded = jwtDecode(token);
3202
3258
  if (decoded.exp ? decoded.exp * 1000 < Date.now() : false) {
3203
3259
  handleError("TOKEN_EXPIRED", false);
3204
3260
  await refresh();
@@ -3215,15 +3271,20 @@ function useAuth(secretKey = getSecretKey()) {
3215
3271
  }
3216
3272
  };
3217
3273
  return {
3274
+ getJwt,
3275
+ getRefreshToken,
3218
3276
  isAuthenticated,
3219
- jwt,
3220
- refresh_token,
3221
- tokenExpiry,
3277
+ getTokenExpiry,
3222
3278
  login,
3223
3279
  refresh,
3224
3280
  logout,
3225
3281
  cleanStorage,
3226
3282
  verifyToken,
3283
+ ab2hex,
3284
+ hex2ab,
3285
+ importKey,
3286
+ encrypt,
3287
+ decrypt,
3227
3288
  };
3228
3289
  }
3229
3290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arex95/vue-core",
3
- "version": "1.1.15",
3
+ "version": "1.1.19",
4
4
  "description": "Opinionated Vue Core",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,7 +15,10 @@
15
15
  "scripts": {
16
16
  "build": "rollup -c",
17
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 package-lock.json && git commit -m \"chore(release): update changelog\" && git push && npm publish --access public"
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"
19
22
  },
20
23
  "repository": {
21
24
  "type": "git",
@@ -46,15 +49,19 @@
46
49
  "@rollup/plugin-typescript": "^12.1.2",
47
50
  "@types/crypto-js": "^4.2.2",
48
51
  "@types/node": "^22.13.10",
52
+ "@vitest/ui": "^3.2.3",
53
+ "@vue/test-utils": "^2.4.6",
49
54
  "conventional-changelog-cli": "^5.0.0",
50
55
  "eslint": "^9.23.0",
51
56
  "eslint-plugin-vue": "^10.0.0",
52
57
  "globals": "^16.0.0",
58
+ "jsdom": "^26.1.0",
53
59
  "rollup": "^4.35.0",
54
60
  "ts-node": "^10.9.2",
55
61
  "tslib": "^2.8.1",
56
62
  "typescript": "^5.8.2",
57
- "typescript-eslint": "^8.28.0"
63
+ "typescript-eslint": "^8.28.0",
64
+ "vitest": "^3.2.3"
58
65
  },
59
66
  "dependencies": {
60
67
  "@tanstack/vue-query": ">=5.0.0",