I want to use this:

```
// src/services/authTokenExpiryServices.ts
import { AsyncCache } from '../utils/asyncCacheUtils';
import { CompositeCacheStorage } from '../utils/compositeCacheStorageUtils';
import { LocalStorageCacheStorage } from '../utils/localStorageCacheStorageUtils';
import { MemoryCacheStorage } from '../utils/memoryCacheStorageUtils';


// You can adjust the storage key and TTL as needed
const STORAGE_KEY = 'authTokenExpiryCache';

/**
* Instantiate storage layers: memory → localStorage (skip IndexedDB for simple value)
*/
const memoryStorage = new MemoryCacheStorage<number>();
const localStorageStorage = new LocalStorageCacheStorage<number>(STORAGE_KEY);

const compositeStorage = new CompositeCacheStorage<number>([
	memoryStorage,
	localStorageStorage,
]);

/**
* TTL for expiry timestamp cache. This can be long because the expiry timestamp itself is authoritative.
* For example: 24 hours, can be higher if you trust backend expiry consistently
*/
const TTL_MS = 24 * 60 * 60 * 1000;

export const accessTokenExpiryCache = new AsyncCache<number>(TTL_MS, compositeStorage);

/**
* Store the expiresAt timestamp in cache
*/
export async function storeAccessTokenExpiry(expiresAtMs: number): Promise<void>
{
	// Using keyless AsyncCache means one value per instance, so empty key is fine
	await accessTokenExpiryCache.set(expiresAtMs);
}

/**
* Get the expiresAt timestamp from cache
*/
export async function getAccessTokenExpiry(): Promise<number | null>
{
	const expiry = await accessTokenExpiryCache.get(null as any);// key is ignored internally
	return expiry ?? null;
}

/**
* Clear cached expiresAt timestamp
*/
export async function clearAccessTokenExpiry(): Promise<void>
{
	await accessTokenExpiryCache.remove(null as any);
}
```

that is able to store the expireAt data being sent from my backend api that handles the JWT token. It needs to work with my existing code:

```
// src/services/apiClientServices.ts
import axios, { AxiosRequestConfig } from 'axios';
import { API_BASE_URL } from '../constants/apiConstants';
// TODO - import { emitEvent } from '../utils/eventBusUtils';
import { csrfTokenManager } from './csrfServices';
// authTokenExpiryServices.ts - Store "ExpireAt" data for JWT Secure Token refresh
import { getAccessTokenExpiry, storeAccessTokenExpiry, clearAccessTokenExpiry } from './authTokenExpiryServices';
// authServices.ts - Refresh JWT Secure Token from backend
import { tryRefreshToken } from './authServices';
// nativeTokenStorageServices.ts - React Native App Secure Authorization Token storage
import { getAccessTokenNative, getAccessTokenExpiryNative, clearTokensNative } from './nativeTokenStorageServices';
import logger from '../utils/loggerUtils';


/**
* Api Client Services
*
* Example usage of callApiWithAuth
*	
*	import { callApiWithAuth } from './apiClientServices';
*	
*	async function submitSignupStageOneVerify(data: any)
*	{
*		return callApiWithAuth((token) =>
*			apiClient.post('/api-signup/stage-one-verify', data,
*			{
*				headers:
*				{
*					Authorization: `Bearer ${token}`,
*				},
*			})
*		);
*	}
**
* API Return Structure for All Responses:
*
* 'success' => bool
* - Required boolean indicating the overall success (true) or failure (false) of the request.
*
* 'message' => string|null
* - Optional human-readable summary message intended for general user feedback.
*   For example: "Signup completed successfully." or "Validation failed. Please review errors."
*
* 'errors' => array|null
* - Optional detailed field-level errors provided as an associative array keyed by attribute name,
*   e.g. ['email' => ['Email already registered'], 'password' => ['Password too weak']]
* - Should be included typically for validation failures or form errors.
* - Should NOT aggregate all field errors into this or the 'message' string.
*
* 'data' => array|null
* - Optional container for domain-specific returned data relevant to the successful request,
*   such as user IDs, signup phases, tokens, or other payload.
*   Example: ['userId' => 123, 'signup_phase' => 2]
*
* Guidelines:
* - Always use the 'success' boolean to convey request outcome; do NOT rely on other status indicators.
* - Always include a clear and concise 'message' to aid frontend user messaging.
* - Only include 'errors' when there are validation or field-specific errors to display inline.
* - Group all additional return values inside the 'data' object to keep the response consistent.
* - Use appropriate HTTP status codes (e.g. 422 for validation errors, 200 for success) together
*   with this body structure for clarity and RESTfulness.
*
* Example error response:
* 	[
*		'success' => false,
*		'message' => 'Validation failed. Please review the errors.',
* 		'errors' => $model->getErrors(),
* 		// 'data' omitted
* 	]
*
* Example success response:
* 	[
* 		'success' => true,
*		'message' => 'Signup completed successfully.',
* 		'errors' => [],// or omitted
* 		'data' => [
*  			'userId' => $user->id,
* 			'signup_phase' => $user->signup_phase,
* 		]
*	 ]
*/
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:5000/api';

const apiClient = axios.create({
	baseURL: API_BASE_URL,
	withCredentials: true,// sends cookies automatically with cross-origin requests
	headers:
	{
		'Content-Type': 'application/json',
	},
	timeout: 10000,// Aborts the request and throws an error after 10 seconds
});

/**
* Axios request interceptor
* Axios interceptors allow you to intercept and modify requests or responses globally before 
* they are handled by your individual API calls.
*
* - Add CSRF token if mutating method,
* - Proactively refresh token if expired or about to expire,
* - Attach Authorization header.
*/
apiClient.interceptors.request.use(async (config: AxiosRequestConfig) =>
{
	/**
	* Read token from the non-HttpOnly cookie or from csrf-token endpoint
	* add X-CSRF-Token header to mutating requests (POST, PUT, PATCH, DELETE)
	*/
	const method = (config.method || '').toLowerCase();
	
	if (['post', 'put', 'patch', 'delete'].includes(method))
	{
		const csrfToken = csrfTokenManager.getRawToken();
		
		if (csrfToken)
		{
			config.headers = config.headers || {};
			config.headers['X-CSRF-Token'] = csrfToken;
		}
	}
	
	// Proactively check token expiry before request
	try
	{
		const expiryTimestamp = await getAccessTokenExpiryNative();
		const now = Date.now();
		const buffer = 2 * 60 * 1000;// 2 minutes buffer
		
		if (expiryTimestamp && now + buffer >= expiryTimestamp)
		{
			const refreshed = await tryRefreshToken();
			
			if (!refreshed)
			{
				await clearTokensNative();
				
				// TODO - emitEvent('alert', { type: 'danger', key: 'alert.unauthorized' });
				
				throw new axios.Cancel('Access token expired and refresh failed.');
			}
		}
	}
	catch (error)
	{
		logger.error('apiClientServices.ts', 'Error during proactive token refresh:', error);
		
		// Let request continue, low-level error here will fallback on 401 handling.
	}
	
	/**
	* Authorization header is only needed for Native React App. Web browser
	* based apps store JWT token within cookies using HttpOnly.
	*
	* JWT in an HTTP-only Secure Cookie
	* If you store the JWT in an HTTP-only secure cookie and use `withCredentials: true`,
	* the browser automatically sends the cookie with every request.
	*
	* In this case, manually attaching the Authorization header with the JWT 
	* from localStorage is redundant and defeats the purpose of using HTTP-only cookies,
	* since HTTP-only cookies are inaccessible to JavaScript.
	*
	* To maintain security best practices and avoid token duplication, never store
	* tokens in localStorage, indexedDB, Sessions and memory. Storing JWT tokens 
	* exposes teh app to XSS attacks, since malicious scripts could access these
	* storages.
	*/
	const token = await getAccessTokenNative();
	
	if (token && config.headers)
	{
		config.headers['Authorization'] = `Bearer ${token}`;
	}//*/
	
	return config;
});

/**
* Axios response interceptor
* Add response interceptor to handle errors globally, refresh tokens, etc.
*
* - Handles errors globally,
* - On first 401, tries to refresh token then retry request,
* - If refresh fails, clear tokens and redirect to login.
*/
apiClient.interceptors.response.use
(
	(response) => response,// Pass through successful responses unchanged
	
	async (error) =>
	{
		// Handle Axios timeout errors explicitly
		if (error.code === 'ECONNABORTED' && error.message.includes('timeout'))
		{
			logger.warn('apiClientServices.ts', 'Request timed out:', error);
			
			// TODO - emitEvent('alert', { type: 'warning', key: 'alert.timeout' });
			
			return Promise.reject(new Error('Request timed out. Please try again.'));
		}
		
		// Otherwise handle by HTTP status code
		const status = error.response?.status;
		
		/**
		* 401 Outside Switch and case (loop)
		* When a request returns a 401 Unauthorized, you want to:
		* - Attempt to refresh the token (e.g., get a new JWT cookie).
		* - Retry the original request once with the new token.
		* - If the retry also returns 401 (e.g., refresh failed or token invalid), 
		*   do not keep retrying indefinitely — instead redirect to login or show error.
		*
		* TODO - Signup needs to ignore this.
		*/
		const originalRequest = error.config;
		
		if (status === 401 && !originalRequest?._retry && !originalRequest.url.includes('/signup/'))
		{
			originalRequest._retry = true;// Prevent infinite loop
			
			logger.warn('apiClientServices.ts', '401 Unauthorized received, attempting token refresh');
			
			try
			{
				const refreshed = await tryRefreshToken();
				
				if (refreshed)
				{
					logger.info('apiClientServices.ts', 'Token refreshed successfully, retrying original request');
					
					return apiClient(originalRequest);
				}
				else
				{
					logger.error('apiClientServices.ts', 'Refresh failed, clearing tokens and redirecting');
					
					await clearTokensNative();
					
					// TODO - emitEvent('alert', { type: 'danger', key: 'alert.unauthorized' });
					
					window.location.href = '/login';
					
					return Promise.reject(error);
				}
			}
			catch (refreshError)
			{
				logger.error('apiClientServices.ts', 'Error during token refresh:', refreshError);
				
				await clearTokensNative();
				
				// TODO - emitEvent('alert', { type: 'danger', key: 'alert.unauthorized' });
				
				window.location.href = '/login';
				
				return Promise.reject(error);
			}
		}//*/
		
		// Other status code specific handling for logging and alerts
		switch (status)
		{
			case 400 :
			{
				// Bad Request — maybe validation failed
				logger.warn('apiClientServices.ts', '400 Bad Request:', error);
				
				// TODO - emitEvent('alert', { type: 'warning', key: 'alert.badRequest' });
				
				break;
			}
			//case 401 : {}
			case 403 :
			{
				// Forbidden — user lacks permissions
				logger.warn('apiClientServices.ts', '403 Forbidden:', error);
				
				// TODO - emitEvent('alert', { type: 'danger', key: 'alert.forbidden' });
				
				// Maybe redirect or show permission error dialog
				
				break;
			}
			case 404 :
			{
				// Not Found — resource missing
				logger.info('apiClientServices.ts', '404 Not Found:', error);
				
				// TODO - emitEvent('alert', { type: 'info', key: 'alert.notFound' });
				
				break;
			}
			case 500 :
			{
				// Server error — inform user, retry options, etc
				logger.error('apiClientServices.ts', '500 Server Error:', error);
				
				// TODO - emitEvent('alert', { type: 'danger', key: 'alert.serverError' });
				
				break;
			}	
			default :
			{
				// Other errors: network issues, etc.
				logger.error('apiClientServices.ts', 'Unhandled error:', error);
				
				// TODO - emitEvent('alert', { type: 'danger', key: 'alert.unknownError' });
			}
		}
		
		// handle 401, token refresh, global error logging here
		return Promise.reject(error);
	}
);

export default apiClient;


// src/services/nativeTokenStorageServices.ts
/**
* Expo Secure Store
*
* `expo-secure-store` is a secure, encrypted key-value storage library for React Native apps 
* (particularly those built with Expo). It enables safe storage of sensitive data like JWT 
* access and refresh tokens by using the device's secure storage mechanisms:
*   - **iOS Keychain** on iOS devices
*   - **Android Keystore** on Android devices
*
* This significantly reduces exposure to attacks compared to plain AsyncStorage or localStorage,
* which are vulnerable to extraction by malicious apps or scripts.
*
* ### Why use it?
* - Stores data encrypted and protected at OS level.
* - Prevents unauthorized access to tokens.
* - Essential for meeting security best practices in native mobile apps.
*
* ### Installation:
* In an Expo managed workflow:
* ```
* expo install expo-secure-store
* ```
* For bare React Native projects:
* - Follow installation and linking instructions here: 
*   https://docs.expo.dev/versions/latest/sdk/securestore/#bare-react-native-projects
*
* ### Documentation:
* - Official Expo docs: https://docs.expo.dev/versions/latest/sdk/securestore/
*
* ### Alternatives if not using Expo:
* - [`react-native-keychain`](https://github.com/oblador/react-native-keychain) — iOS/Android Keychain/Keystore integration
* - [`react-native-sensitive-info`](https://github.com/mCodex/react-native-sensitive-info) — cross-platform secure storage
*
* ### Important security notes:
* - On the **web**, you should *never* store JWTs in localStorage or sessionStorage due to XSS risks.
*   Instead, store tokens in **HttpOnly, Secure cookies** (as your current setup does).
* - On **native apps**, use secure storage (like `expo-secure-store`) to protect tokens.
* - This strategy helps you safely support both web (cookie-based) and native (secure store + auth header) authentication flows.
*/
//TODO - import * as SecureStore from 'expo-secure-store';


const ACCESS_TOKEN_KEY = 'accessToken';
const REFRESH_TOKEN_KEY = 'refreshToken';
const EXPIRES_AT_KEY = 'accessTokenExpiresAt';

/**
* Get the stored access token from secure storage.
*/
export async function getAccessTokenNative(): Promise<string | null>
{
	console.log('nativeTokenStorage: getAccessToken called');
	/*try
	{
		return await SecureStore.getItemAsync(ACCESS_TOKEN_KEY);
	}
	catch (error)
	{
		console.error('nativeTokenStorage: getAccessToken failed', error);
		
		return null;
	}*/
	
	return null;
}

/**
* Store the access token securely.
*/
export async function setAccessTokenNative(token: string): Promise<void>
{
	console.log('nativeTokenStorage: setAccessToken called', token);
	/*try
	{
		await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, token);
	}
	catch (error)
	{
		console.error('nativeTokenStorage: setAccessToken failed', error);
	}*/
}

/**
* Get the stored refresh token from secure storage.
*/
export async function getRefreshTokenNative(): Promise<string | null>
{
	console.log('nativeTokenStorage: getRefreshToken called');
	/*try
	{
		return await SecureStore.getItemAsync(REFRESH_TOKEN_KEY);
	}
	catch (error)
	{
		console.error('nativeTokenStorage: getRefreshToken failed', error);
		
		return null;
	}*/
	
	return null;
}

/**
* Store the refresh token securely.
*/
export async function setRefreshTokenNative(token: string): Promise<void>
{
	console.log('nativeTokenStorage: setRefeshToken called', token);
	/*try
	{
		await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, token);
	}
	catch (error)
	{
		console.error('nativeTokenStorage: setRefreshToken failed', error);
	}*/
}

/**
* Get the stored access token expiresAt timestamp (milliseconds).
*/
export async function getAccessTokenExpiryNative(): Promise<number | null>
{
	console.log('nativeTokenStorage: getAccessTokenExpiry called');
	/*try
	{
		const stored = await SecureStore.getItemAsync(EXPIRES_AT_KEY);
		
		if (stored)
		{
			const parsed = Number(stored);
			
			return isNaN(parsed) ? null : parsed;
		}
		
		return null;
	}
	catch (error)
	{
		console.error('nativeTokenStorage: getAccessTokenExpiry failed', error);
		
		return null;
	}*/
	
	return null;
}

/**
* Store the access token expiresAt timestamp (milliseconds).
*/
export async function setAccessTokenExpiryNative(expiresAtMs: number): Promise<void>
{
	console.log('nativeTokenStorage: setAccessTokenExpiry called', expiresAtMs);
	/*try
	{
		await SecureStore.setItemAsync(EXPIRES_AT_KEY, expiresAtMs.toString());
	}
	catch (error)
	{
		console.error('nativeTokenStorage: setAccessTokenExpiry failed', error);
	}*/
}

/**
* Clear both access and refresh tokens from secure storage.
*/
export async function clearTokensNative(): Promise<void>
{
	console.log('nativeTokenStorage: clearTokens called');
	/*try
	{
		await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY);
		await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY);
		await SecureStore.deleteItemAsync(EXPIRES_AT_KEY);
	}
	catch (error)
	{
		console.error('nativeTokenStorage: clearTokens failed', error);
	}*/
}


// src/services/authServices.ts
import apiClient from './apiClientServices';
import { ENDPOINTS } from '../constants/apiConstants';
// nativeTokenStorageServices.ts - React Native App Secure Authorization Token storage
import {
	getRefreshTokenNative,
	setAccessTokenNative,
	setRefreshTokenNative,
	clearTokensNative,
} from './nativeTokenStorageServices';
import logger from '../utils/loggerUtils';


/**
* Attempts to refresh the JWT Secure Token.
* - Web: refresh via cookie-based endpoint (no token in body)
* - Native: refresh via sending refresh token explicitly and store new tokens securely
* 
* Currently, native support is commented out — uncomment to activate when ready.
*/
export async function tryRefreshToken(): Promise<boolean>
{
	try
	{
		// POST refresh token request - body empty, cookies sent automatically
		const response = await apiClient.post(ENDPOINTS.security.refreshJwtToken, {},
		{
			withCredentials: true,// important to send cookies
		});
		
		// token refreshed via cookies
		if (response.data?.success === true)
		{
			return true;
		}
		else
		{
			logger.warn('authServices.ts', 'Web refresh token response unsuccessful:', response.data);
			
			return false;
		}
	}
	catch (error)
	{
		logger.error('authServices.ts', 'Web token refresh failed, attempting native refresh if enabled:', error);
		
		/**
		* Native App JWT Refresh flow (planned for future)
		* 1. Get refresh token stored securely in native storage (e.g., SecureStore)
		*/
		try
		{
			const refreshToken = await getRefreshTokenNative();
			
			if (!refreshToken)
			{
				logger.warn('authServices.ts', 'No refresh token found in native storage');
				
				return false;
			}
			
			// 2. Call refresh endpoint sending refresh token explicitly in POST body (no cookies)
			const nativeResponse = await apiClient.post(ENDPOINTS.security.refreshJwtToken,
			{
				refreshToken: refreshToken
			},{
				withCredentials: false,
				//withCredentials: true,// sends cookies automatically with cross-origin requests
			});
			
			if (nativeResponse.data?.success)
			{
				// 3. Store new access and refresh tokens in native secure storage explicitly
				if (nativeResponse.data.access_token)
				{
					await setAccessTokenNative(nativeResponse.data.access_token);
				}
				if (nativeResponse.data.refresh_token)
				{
					await setRefreshTokenNative(nativeResponse.data.refresh_token);
				}
				
				return true;
			}
			else
			{
				logger.warn('authServices.ts', 'Native refresh failed:', nativeResponse.data);
				
				await clearTokensNative();
				
				return false;
			}
		}
		catch (nativeError)
		{
			logger.error('authServices.ts', 'Native token refresh error:', nativeError);
			
			await clearTokensNative();
			
			return false;
		}//*/
		
		// If native support is not enabled, just fail here
		return false;
	}
}


// src/services/csrfServices.ts
import Cookies from 'js-cookie';
import apiClient from './apiClientServices';
import { ENDPOINTS } from '../constants/apiConstants';
import logger from '../utils/loggerUtils';


/**
* CSRF Token manager class.
* Handles fetching token from backend, reading from cookie,
* caching token internally, and providing easy access.
*
* On app startup, call:
* 	
* 	await csrfTokenManager.init();
* 
* In your frontend app initialization code (e.g. top-level entry 
* or React app startup):
* 
* 	import { csrfTokenManager } from './services/csrfServices';
* 	
* 	async function bootstrapApp()
* 	{
* 		await csrfTokenManager.init();
* 		// ... continue app startup
* 	}
* 	
* 	bootstrapApp();
*
* Set up Axios interceptor to add CSRF header automatically
* 	
* 	import apiClient from './services/apiClientServices';
* 	import { csrfTokenManager } from './services/csrfServices';
* 	
* 	apiClient.interceptors.request.use(config =>
* 	{
* 		const token = csrfTokenManager.getRawToken();
* 		
* 		if (token)
* 		{
* 			config.headers = config.headers || {};
* 			config.headers['X-CSRF-Token'] = token;
* 		}
* 		
* 		return config;
* 	});
*
* Example usage in React component
*
* 	import React, { useState, useEffect } from 'react';
* 	import { csrfTokenManager } from '../services/csrfServices';
* 	
* 	function MyForm()
* 	{
* 		const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
* 		const [csrfParam, setCsrfParam] = useState<string | undefined>(undefined);
* 	
* 		useEffect(() =>
* 		{
* 			// Fetch tokens from the csrfTokenManager singleton
* 			setCsrfToken(csrfTokenManager.getRawToken());
* 			setCsrfParam(csrfTokenManager.getParam());
* 		}, []); // empty deps so runs once on mount
* 	
* 		return (
* 			<form method="post" action="/submit-endpoint">
* 				{csrfParam && csrfToken && (
* 					<input type="hidden" name={csrfParam} value={csrfToken} />
* 				)}
* 				<button type="submit">Submit</button>
* 			</form>
* 		);
* 	}
* 	
* 	export default MyForm;
*/
export const CSRF_COOKIE_NAME = 'csrf';

export type CsrfData =
{
	csrfParam: string;// e.g. "_csrf"
	csrfToken: string;// raw CSRF TOKEN string from backend JSON repsonse
};

/**
* Helper to wait for a cookie to become available within a timeout.
* Polls every 50ms until cookie exists or timeout reached.
*/
async function waitForCookie(name: string, timeoutMs = 2000): Promise<string | undefined>
{
	return new Promise(resolve =>
	{
		const interval = 50;
		let elapsed = 0;
		
		const timer = setInterval(() =>
		{
			const value = Cookies.get(name);
			
			if (value)
			{
				clearInterval(timer);
				resolve(value);
			}
			
			elapsed += interval;
			
			if (elapsed >= timeoutMs)
			{
				clearInterval(timer);
				resolve(undefined);
			}
		}, interval);
	});
}

/**
* Fetch CSRF token and parameter from backend JSON API.
* Backend also sets signed CSRF cookie separately.
*
* Throws an error if token or param are missing,
* or if cookie is missing after waiting.
*/
export async function fetchCsrfToken(): Promise<CsrfData>
{
	const response = await apiClient.get(ENDPOINTS.security.csrfToken);
	const { csrfParam, csrfToken } = response.data;
	
	if (!csrfParam || !csrfToken)
	{
		throw new Error('Failed to fetch CSRF token from backend response.');
	}
	
	// Wait for cookie up to 2 seconds for it to be available
	const cookieToken = await waitForCookie(CSRF_COOKIE_NAME, 2500);
	
	// Ensure the correct CSRF token from cookie is grabbed
	//const cookieToken = getSignedCsrfTokenFromCookie();
	
	if (!cookieToken)
	{
		throw new Error('CSRF token cookie missing.');
	}
	
	/**
	* The backend sends the CSRF token in the JSON response as a plain string.
	*
	* While the backend also sets a CSRF cookie (e.g., named 'csrfToken'), this cookie's value
	* is signed and serialized by the Yii framework to prevent tampering.
	* That means the cookie does NOT contain the raw token string but a protected value
	* including a signature and serialized data.
	* 
	* Because of that, attempting to read the CSRF token directly from the cookie on the frontend
	* and compare it with the token from backend JSON response will fail due to mismatched formats.
	* 
	* Therefore, it is best practice to trust the backend JSON token directly for sending in headers 
	* and not attempt to read or validate the CSRF token from the cookie value.
	* 
	* This approach avoids dealing with Yii's cookie signing mechanics on the client side,
	* simplifies the CSRF token management,
	* and ensures synchronization without false mismatches.
	*/
	logger.log('csrfServices.ts', 'CSRF token from backend response:', csrfToken);
	logger.log('csrfServices.ts', 'CSRF token from cookie:', cookieToken);
	
	return { csrfParam, csrfToken };
}

/**
* Read the CSRF token directly from the CSRF cookie.
* This is a signed and serialized value, not raw token!
*/
export function getSignedCsrfTokenFromCookie(): string | undefined
{
	const cookieValue = Cookies.get(CSRF_COOKIE_NAME);
	
	logger.log('csrfServices.ts', `CSRF Cookie ${CSRF_COOKIE_NAME} value:`, cookieValue);

	return cookieValue;
}

/**
* Clear old CSRF cookie
*/
export async function clearCsrfCookie(): Promise<void>
{
	Cookies.remove(CSRF_COOKIE_NAME, { path: '/' });
}

/**
* CSRF Token Manager - singleton class instance.
* Handles fetching, caching, and providing raw token & cookie.
*/
class CsrfTokenManager
{
	private csrfParam?: string;
	private csrfToken?: string;// Raw token from backend JSON (for headers)
	private csrfCookieToken?: string;// Signed token from cookie (read-only)
	private initPromise?: Promise<void>;
	
	/**
	* Initialize by fetching CSRF token from backend and reading cookie token.
	* Must be called once on app startup, e.g. `await csrfTokenManager.init()`.
	*/
	async init(): Promise<void>
	{
		if (this.initPromise)
		{
			return this.initPromise;
		}
		
		this.initPromise = (async () =>
		{
			try
			{
				const { csrfParam, csrfToken } = await fetchCsrfToken();
			
				this.csrfParam = csrfParam;// Typically "_csrf"
				this.csrfToken = csrfToken;
			}
			catch (error)
			{
				logger.error('csrfServices.ts', 'Failed to fetch CSRF tokens during init:', error);
				
				throw error;
			}
			
			// Always read the signed cookie token fresh
			this.csrfCookieToken = getSignedCsrfTokenFromCookie();
		})();
		
		return this.initPromise;
	}
	
	/**
	* Get the raw CSRF token (backend JSON) used for API 
	* request header `X-CSRF-Token`.
	*/
	getRawToken(): string | undefined
	{
		return this.csrfToken;
	}
	
	/**
	* Get the signed CSRF token value read from cookie.
	*
	* Note: Usually unnecessary for API requests, but available if needed.
	*/
	getCookieToken(): string | undefined
	{
		return this.csrfCookieToken;
	}
	
	/**
	* Get CSRF param name (usually '_csrf', needed for form hidden inputs).
	*/
	getParam(): string | undefined
	{
		return this.csrfParam;
	}
	
	/* Optional manual setter, e.g. if you want to manually update tokens.
	setTokens(rawToken?: string, param?: string, cookieToken?: string): void
	{
		if (rawToken)
		{
			this.csrfToken = rawToken;
		}
		if (param)
		{
			this.csrfParam = param;
		}
		if (cookieToken)
		{
			this.csrfCookieToken = cookieToken;
		}
	}//*/
}

// Singleton instance of the CSRF token manager
export const csrfTokenManager = new CsrfTokenManager();

```