@@ -7,10 +7,13 @@ import StorageService from "../storage/storageService";
77import HelperServices from "../helpers/helperServices" ;
88import {
99 APPLICATION_NAME ,
10- APP_BASE_ROUTE
10+ APP_BASE_ROUTE ,
11+ DEFAULT_FORMIO_JWT_EXPIRE ,
12+ FORMIO_JWT_EXPIRE ,
1113} from "../constants/constants" ;
14+ import { getUpdatedJwtToken } from "../request/jwtTokenService" ;
1215
13- class KeycloakService {
16+ class KeycloakService {
1417 /**
1518 * Used to create Keycloak object
1619 */
@@ -20,8 +23,9 @@ import {
2023 private token : string | undefined ;
2124 private _tokenParsed : KeycloakTokenParsed | undefined ;
2225 private timerId : any = 0 ;
23- private userData : any
24- private isInitialized : boolean = false ; // Track if Keycloak is initialized
26+ private static jwtTimerId : any = 0 ;
27+ private userData : any ;
28+ private isInitialized : boolean = false ; // Track if Keycloak is initialized
2529
2630 private constructor ( url : string , realm : string , clientId : string , tenantId ?: string ) {
2731 this . _keycloakConfig = {
@@ -166,6 +170,87 @@ import {
166170 this . keycloackUpdateToken ( ) ;
167171 } ;
168172
173+ /**
174+ * Extracts the JWT token from the response headers and saves it
175+ * to local storage for future authenticated requests.
176+ *
177+ * @param response - The HTTP response containing the JWT token in headers.
178+ */
179+ private static saveJwtToken ( response : any ) : void {
180+ const jwtToken = response . headers [ "x-jwt-token" ] ;
181+ if ( jwtToken ) {
182+ StorageService . save ( StorageService . User . FORMIO_TOKEN , jwtToken ) ;
183+ }
184+ }
185+
186+ /**
187+ * Fetches a new JWT token using the `getUpdatedJwtToken()` method and
188+ * stores it using `saveJwtToken()`. This is useful for refreshing
189+ * the token periodically or on session extension.
190+ */
191+ public static async updateJwtToken ( ) : Promise < void > {
192+ try {
193+ const response = await getUpdatedJwtToken ( ) ;
194+ if ( response ) {
195+ this . saveJwtToken ( response ) ;
196+ }
197+ } catch ( error ) {
198+ console . error ( "Failed to update JWT token" , error ) ;
199+ }
200+ }
201+
202+ /**
203+ * Stops the polling mechanism for refreshing the JWT token
204+ * by clearing the interval timer.
205+ */
206+ private static clearPolling ( ) : void {
207+ if ( this . jwtTimerId ) {
208+ clearInterval ( this . jwtTimerId ) ;
209+ this . jwtTimerId = 0 ;
210+ console . log ( "Polling stopped." ) ;
211+ }
212+ }
213+
214+
215+ /**
216+ * Starts a background polling mechanism to refresh the JWT token periodically before it expires.
217+ *
218+ * - Retrieves the JWT expiration interval from the environment/config (`FORMIO_JWT_EXPIRE`).
219+ * - If the value is invalid or missing, falls back to a default (`DEFAULT_FORMIO_JWT_EXPIRE`).
220+ * - Adds a small buffer (2 seconds) to ensure the token is refreshed slightly before actual expiration.
221+ * - Clears any existing polling interval before starting a new one.
222+ * - Sets up a `setInterval` that checks for online status and asynchronously refreshes the JWT token.
223+ * - If `skipTimer` is true or the application is not `"roadsafety"`, the interval is set to 0 (executes immediately).
224+ *
225+ * @param skipTimer - If true, bypasses the default interval check (used for forced/immediate refresh).
226+ */
227+ private static refreshJwtToken ( skipTimer : boolean = false ) : void {
228+ const parsedValue = Number ( FORMIO_JWT_EXPIRE ) ;
229+ const jwtExpireMinutes = isNaN ( parsedValue )
230+ ? DEFAULT_FORMIO_JWT_EXPIRE
231+ : parsedValue ;
232+
233+ // 2 seconds buffer (2000 ms)
234+ const checkInterval = jwtExpireMinutes * 60 * 1000 - 2000 ;
235+
236+ this . clearPolling ( ) ; // Clear previous interval before starting new
237+
238+ this . jwtTimerId = setInterval (
239+ ( ) => {
240+ ( async ( ) => {
241+ if ( ! navigator . onLine ) {
242+ console . debug ( "Offline: Skipping token refresh." ) ;
243+ return ;
244+ }
245+ await this . updateJwtToken ( ) ;
246+ } ) ( ) ; // Async IIFE inside setInterval
247+ } ,
248+ ! skipTimer && APPLICATION_NAME === "roadsafety" ? checkInterval : 0
249+ ) ;
250+ console . log (
251+ `JWT polling started with interval: ${ checkInterval } ms (${ jwtExpireMinutes } minutes - 2 seconds)`
252+ ) ;
253+ }
169254
170255 /**
171256 *
@@ -218,6 +303,7 @@ import {
218303 "online" ,
219304 ( ) => {
220305 this . retryTokenRefresh ( ) ; // This will be executed when the 'online' event occurs
306+ KeycloakService . updateJwtToken ( ) ;
221307 } ,
222308 { once : false }
223309 ) ;
@@ -235,6 +321,7 @@ import {
235321 callback ( true ) ;
236322 } ) ;
237323 this . refreshToken ( ) ;
324+ KeycloakService . refreshJwtToken ( ) ;
238325 }
239326 }
240327 else {
@@ -254,6 +341,7 @@ import {
254341 */
255342 public userLogout ( ) : void {
256343 this . isInitialized = false ; // Reset initialization state
344+ KeycloakService . clearPolling ( ) ;
257345 StorageService . clear ( ) ;
258346 this . logout ( ) ;
259347 }
0 commit comments