@@ -5,18 +5,68 @@ import { Token } from './token';
5
5
* The configuration for retrying token refreshes.
6
6
*/
7
7
export interface RetryPolicy {
8
- // The maximum number of attempts to retry token refreshes.
8
+ /**
9
+ * The maximum number of attempts to retry token refreshes.
10
+ */
9
11
maxAttempts : number ;
10
- // The initial delay in milliseconds before the first retry.
12
+
13
+ /**
14
+ * The initial delay in milliseconds before the first retry.
15
+ */
11
16
initialDelayMs : number ;
12
- // The maximum delay in milliseconds between retries (the calculated delay will be capped at this value).
17
+
18
+ /**
19
+ * The maximum delay in milliseconds between retries.
20
+ * The calculated delay will be capped at this value.
21
+ */
13
22
maxDelayMs : number ;
14
- // The multiplier for exponential backoff between retries. e.g. 2 will double the delay each time.
23
+
24
+ /**
25
+ * The multiplier for exponential backoff between retries.
26
+ * @example
27
+ * A value of 2 will double the delay each time:
28
+ * - 1st retry: initialDelayMs
29
+ * - 2nd retry: initialDelayMs * 2
30
+ * - 3rd retry: initialDelayMs * 4
31
+ */
15
32
backoffMultiplier : number ;
16
- // The percentage of jitter to apply to the delay. e.g. 0.1 will add or subtract up to 10% of the delay.
33
+
34
+ /**
35
+ * The percentage of jitter to apply to the delay.
36
+ * @example
37
+ * A value of 0.1 will add or subtract up to 10% of the delay.
38
+ */
17
39
jitterPercentage ?: number ;
18
- // A custom function to determine if a retry should be attempted based on the error and attempt number.
19
- shouldRetry ?: ( error : unknown , attempt : number ) => boolean ;
40
+
41
+ /**
42
+ * Function to classify errors from the identity provider as retryable or non-retryable.
43
+ * Used to determine if a token refresh failure should be retried based on the type of error.
44
+ *
45
+ * Common use cases:
46
+ * - Network errors that may be transient (should retry)
47
+ * - Invalid credentials (should not retry)
48
+ * - Rate limiting responses (should retry)
49
+ *
50
+ * @param error - The error from the identity provider
51
+ * @param attempt - Current retry attempt (0-based)
52
+ * @returns `true` if the error is considered transient and the operation should be retried
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const retryPolicy: RetryPolicy = {
57
+ * maxAttempts: 3,
58
+ * initialDelayMs: 1000,
59
+ * maxDelayMs: 5000,
60
+ * backoffMultiplier: 2,
61
+ * isRetryable: (error) => {
62
+ * // Retry on network errors or rate limiting
63
+ * return error instanceof NetworkError ||
64
+ * error instanceof RateLimitError;
65
+ * }
66
+ * };
67
+ * ```
68
+ */
69
+ isRetryable ?: ( error : unknown , attempt : number ) => boolean ;
20
70
}
21
71
22
72
/**
@@ -36,14 +86,13 @@ export interface TokenManagerConfig {
36
86
}
37
87
38
88
/**
39
- * IDPError is an error that occurs while calling the underlying IdentityProvider .
89
+ * IDPError indicates a failure from the identity provider .
40
90
*
41
- * It can be transient and if retry policy is configured, the token manager will attempt to obtain a token again.
42
- * This means that receiving non-fatal error is not a stream termination event.
43
- * The stream will be terminated only if the error is fatal.
91
+ * The `isRetryable` flag is determined by the RetryPolicy's error classification function - if an error is
92
+ * classified as retryable, it will be marked as transient and the token manager will attempt to recover.
44
93
*/
45
94
export class IDPError extends Error {
46
- constructor ( public readonly message : string , public readonly isFatal : boolean ) {
95
+ constructor ( public readonly message : string , public readonly isRetryable : boolean ) {
47
96
super ( message ) ;
48
97
this . name = 'IDPError' ;
49
98
}
@@ -105,7 +154,6 @@ export class TokenManager<T> {
105
154
*/
106
155
public start ( listener : TokenStreamListener < T > , initialDelayMs : number = 0 ) : Disposable {
107
156
if ( this . listener ) {
108
- console . log ( 'TokenManager is already running, stopping the previous instance' ) ;
109
157
this . stop ( ) ;
110
158
}
111
159
@@ -142,14 +190,14 @@ export class TokenManager<T> {
142
190
private shouldRetry ( error : unknown ) : boolean {
143
191
if ( ! this . config . retry ) return false ;
144
192
145
- const { maxAttempts, shouldRetry } = this . config . retry ;
193
+ const { maxAttempts, isRetryable } = this . config . retry ;
146
194
147
195
if ( this . retryAttempt >= maxAttempts ) {
148
196
return false ;
149
197
}
150
198
151
- if ( shouldRetry ) {
152
- return shouldRetry ( error , this . retryAttempt ) ;
199
+ if ( isRetryable ) {
200
+ return isRetryable ( error , this . retryAttempt ) ;
153
201
}
154
202
155
203
return false ;
@@ -172,16 +220,16 @@ export class TokenManager<T> {
172
220
if ( this . shouldRetry ( error ) ) {
173
221
this . retryAttempt ++ ;
174
222
const retryDelay = this . calculateRetryDelay ( ) ;
175
- this . notifyError ( `Token refresh failed (attempt ${ this . retryAttempt } ), retrying in ${ retryDelay } ms: ${ error } ` , false )
223
+ this . notifyError ( `Token refresh failed (attempt ${ this . retryAttempt } ), retrying in ${ retryDelay } ms: ${ error } ` , true )
176
224
this . scheduleNextRefresh ( retryDelay ) ;
177
225
} else {
178
- this . notifyError ( error , true ) ;
226
+ this . notifyError ( error , false ) ;
179
227
this . stop ( ) ;
180
228
}
181
229
}
182
230
}
183
231
184
- private handleNewToken = async ( { token : nativeToken , ttlMs } : TokenResponse < T > ) : Promise < void > = > {
232
+ private async handleNewToken ( { token : nativeToken , ttlMs } : TokenResponse < T > ) : Promise < void > {
185
233
if ( ! this . listener ) {
186
234
throw new Error ( 'TokenManager is not running, but a new token was received' ) ;
187
235
}
@@ -255,13 +303,14 @@ export class TokenManager<T> {
255
303
return this . currentToken ;
256
304
}
257
305
258
- private notifyError = ( error : unknown , isFatal : boolean ) : void => {
306
+
307
+ private notifyError ( error : unknown , isRetryable : boolean ) : void {
259
308
const errorMessage = error instanceof Error ? error . message : String ( error ) ;
260
309
261
310
if ( ! this . listener ) {
262
311
throw new Error ( `TokenManager is not running but received an error: ${ errorMessage } ` ) ;
263
312
}
264
313
265
- this . listener . onError ( new IDPError ( errorMessage , isFatal ) ) ;
314
+ this . listener . onError ( new IDPError ( errorMessage , isRetryable ) ) ;
266
315
}
267
316
}
0 commit comments