@@ -5,18 +5,70 @@ 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
+ * The default behavior is to retry all types of errors if no function is provided.
46
+ *
47
+ * Common use cases:
48
+ * - Network errors that may be transient (should retry)
49
+ * - Invalid credentials (should not retry)
50
+ * - Rate limiting responses (should retry)
51
+ *
52
+ * @param error - The error from the identity provider3
53
+ * @param attempt - Current retry attempt (0-based)
54
+ * @returns `true` if the error is considered transient and the operation should be retried
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const retryPolicy: RetryPolicy = {
59
+ * maxAttempts: 3,
60
+ * initialDelayMs: 1000,
61
+ * maxDelayMs: 5000,
62
+ * backoffMultiplier: 2,
63
+ * isRetryable: (error) => {
64
+ * // Retry on network errors or rate limiting
65
+ * return error instanceof NetworkError ||
66
+ * error instanceof RateLimitError;
67
+ * }
68
+ * };
69
+ * ```
70
+ */
71
+ isRetryable ?: ( error : unknown , attempt : number ) => boolean ;
20
72
}
21
73
22
74
/**
@@ -36,14 +88,13 @@ export interface TokenManagerConfig {
36
88
}
37
89
38
90
/**
39
- * IDPError is an error that occurs while calling the underlying IdentityProvider .
91
+ * IDPError indicates a failure from the identity provider .
40
92
*
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.
93
+ * The `isRetryable` flag is determined by the RetryPolicy's error classification function - if an error is
94
+ * classified as retryable, it will be marked as transient and the token manager will attempt to recover.
44
95
*/
45
96
export class IDPError extends Error {
46
- constructor ( public readonly message : string , public readonly isFatal : boolean ) {
97
+ constructor ( public readonly message : string , public readonly isRetryable : boolean ) {
47
98
super ( message ) ;
48
99
this . name = 'IDPError' ;
49
100
}
@@ -105,7 +156,6 @@ export class TokenManager<T> {
105
156
*/
106
157
public start ( listener : TokenStreamListener < T > , initialDelayMs : number = 0 ) : Disposable {
107
158
if ( this . listener ) {
108
- console . log ( 'TokenManager is already running, stopping the previous instance' ) ;
109
159
this . stop ( ) ;
110
160
}
111
161
@@ -142,14 +192,14 @@ export class TokenManager<T> {
142
192
private shouldRetry ( error : unknown ) : boolean {
143
193
if ( ! this . config . retry ) return false ;
144
194
145
- const { maxAttempts, shouldRetry } = this . config . retry ;
195
+ const { maxAttempts, isRetryable } = this . config . retry ;
146
196
147
197
if ( this . retryAttempt >= maxAttempts ) {
148
198
return false ;
149
199
}
150
200
151
- if ( shouldRetry ) {
152
- return shouldRetry ( error , this . retryAttempt ) ;
201
+ if ( isRetryable ) {
202
+ return isRetryable ( error , this . retryAttempt ) ;
153
203
}
154
204
155
205
return false ;
@@ -172,10 +222,10 @@ export class TokenManager<T> {
172
222
if ( this . shouldRetry ( error ) ) {
173
223
this . retryAttempt ++ ;
174
224
const retryDelay = this . calculateRetryDelay ( ) ;
175
- this . notifyError ( `Token refresh failed (attempt ${ this . retryAttempt } ), retrying in ${ retryDelay } ms: ${ error } ` , false )
225
+ this . notifyError ( `Token refresh failed (attempt ${ this . retryAttempt } ), retrying in ${ retryDelay } ms: ${ error } ` , true )
176
226
this . scheduleNextRefresh ( retryDelay ) ;
177
227
} else {
178
- this . notifyError ( error , true ) ;
228
+ this . notifyError ( error , false ) ;
179
229
this . stop ( ) ;
180
230
}
181
231
}
@@ -255,13 +305,13 @@ export class TokenManager<T> {
255
305
return this . currentToken ;
256
306
}
257
307
258
- private notifyError = ( error : unknown , isFatal : boolean ) : void => {
308
+ private notifyError ( error : unknown , isRetryable : boolean ) : void {
259
309
const errorMessage = error instanceof Error ? error . message : String ( error ) ;
260
310
261
311
if ( ! this . listener ) {
262
312
throw new Error ( `TokenManager is not running but received an error: ${ errorMessage } ` ) ;
263
313
}
264
314
265
- this . listener . onError ( new IDPError ( errorMessage , isFatal ) ) ;
315
+ this . listener . onError ( new IDPError ( errorMessage , isRetryable ) ) ;
266
316
}
267
317
}
0 commit comments