1+ import { msalInstance , loginRequest } from '../authConfig' ;
2+ import { InteractionRequiredAuthError } from '@azure/msal-browser' ;
3+
4+ class TokenRenewalService {
5+ private renewalInterval : NodeJS . Timeout | null = null ;
6+ private isRenewing = false ;
7+ private readonly RENEWAL_INTERVAL = 30 * 60 * 1000 ; // 30 minutes
8+ /**
9+ * Start the token renewal service
10+ */
11+ public start ( ) : void {
12+ if ( this . renewalInterval ) {
13+ this . stop ( ) ; // Clear existing interval
14+ }
15+
16+ console . log ( 'Starting token renewal service...' ) ;
17+
18+ // Initial token check
19+ this . renewTokenIfNeeded ( ) ;
20+
21+ // Set up periodic renewal
22+ this . renewalInterval = setInterval ( ( ) => {
23+ this . renewTokenIfNeeded ( ) ;
24+ } , this . RENEWAL_INTERVAL ) ;
25+ }
26+
27+ /**
28+ * Stop the token renewal service
29+ */
30+ public stop ( ) : void {
31+ if ( this . renewalInterval ) {
32+ clearInterval ( this . renewalInterval ) ;
33+ this . renewalInterval = null ;
34+ console . log ( 'Token renewal service stopped' ) ;
35+ }
36+ }
37+
38+ /**
39+ * Manually trigger token renewal
40+ */
41+ public async renewToken ( ) : Promise < boolean > {
42+ return this . renewTokenIfNeeded ( ) ;
43+ }
44+
45+ /**
46+ * Check if token needs renewal and renew if necessary
47+ */
48+ private async renewTokenIfNeeded ( ) : Promise < boolean > {
49+ if ( this . isRenewing ) {
50+ console . log ( 'Token renewal already in progress, skipping...' ) ;
51+ return false ;
52+ }
53+
54+ try {
55+ this . isRenewing = true ;
56+
57+ const accounts = msalInstance . getAllAccounts ( ) ;
58+ if ( accounts . length === 0 ) {
59+ console . log ( 'No accounts found, cannot renew token' ) ;
60+ return false ;
61+ }
62+
63+ const account = accounts [ 0 ] ;
64+
65+ // Check if token is close to expiry
66+ if ( ! this . isTokenNearExpiry ( account ) ) {
67+ console . log ( 'Token is still valid, no renewal needed' ) ;
68+ return true ;
69+ }
70+
71+ console . log ( 'Token is near expiry, attempting silent renewal...' ) ;
72+
73+ // Attempt silent token renewal
74+ await msalInstance . acquireTokenSilent ( {
75+ ...loginRequest ,
76+ account : account ,
77+ forceRefresh : true , // Force refresh to get new token
78+ } ) ;
79+
80+ console . log ( 'Token renewed successfully via silent request' ) ;
81+ return true ;
82+
83+ } catch ( error ) {
84+ console . warn ( 'Silent token renewal failed:' , error ) ;
85+
86+ if ( error instanceof InteractionRequiredAuthError ) {
87+ console . log ( 'Interactive authentication required - user will need to sign in again on next request' ) ;
88+ // Don't trigger popup automatically as it might be disruptive
89+ // Let the user-triggered request handle the interactive flow
90+ }
91+
92+ return false ;
93+ } finally {
94+ this . isRenewing = false ;
95+ }
96+ }
97+
98+ /**
99+ * Check if the current token is near expiry
100+ */
101+ private isTokenNearExpiry ( _account : any ) : boolean {
102+ // Always attempt renewal for proactive refreshing
103+ // MSAL handles token expiry checks internally, so we'll rely on forceRefresh
104+ return true ;
105+ }
106+
107+ /**
108+ * Get the renewal interval in milliseconds
109+ */
110+ public getRenewalInterval ( ) : number {
111+ return this . RENEWAL_INTERVAL ;
112+ }
113+
114+ /**
115+ * Check if the service is currently running
116+ */
117+ public isRunning ( ) : boolean {
118+ return this . renewalInterval !== null ;
119+ }
120+ }
121+
122+ // Export a singleton instance
123+ export const tokenRenewalService = new TokenRenewalService ( ) ;
0 commit comments