@@ -5,18 +5,70 @@ import { Token } from './token';
55 * The configuration for retrying token refreshes.
66 */
77export interface RetryPolicy {
8- // The maximum number of attempts to retry token refreshes.
8+ /**
9+ * The maximum number of attempts to retry token refreshes.
10+ */
911 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+ */
1116 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+ */
1322 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+ */
1532 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+ */
1739 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 ;
2072}
2173
2274/**
@@ -36,14 +88,13 @@ export interface TokenManagerConfig {
3688}
3789
3890/**
39- * IDPError is an error that occurs while calling the underlying IdentityProvider .
91+ * IDPError indicates a failure from the identity provider .
4092 *
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.
4495 */
4596export class IDPError extends Error {
46- constructor ( public readonly message : string , public readonly isFatal : boolean ) {
97+ constructor ( public readonly message : string , public readonly isRetryable : boolean ) {
4798 super ( message ) ;
4899 this . name = 'IDPError' ;
49100 }
@@ -105,7 +156,6 @@ export class TokenManager<T> {
105156 */
106157 public start ( listener : TokenStreamListener < T > , initialDelayMs : number = 0 ) : Disposable {
107158 if ( this . listener ) {
108- console . log ( 'TokenManager is already running, stopping the previous instance' ) ;
109159 this . stop ( ) ;
110160 }
111161
@@ -142,14 +192,14 @@ export class TokenManager<T> {
142192 private shouldRetry ( error : unknown ) : boolean {
143193 if ( ! this . config . retry ) return false ;
144194
145- const { maxAttempts, shouldRetry } = this . config . retry ;
195+ const { maxAttempts, isRetryable } = this . config . retry ;
146196
147197 if ( this . retryAttempt >= maxAttempts ) {
148198 return false ;
149199 }
150200
151- if ( shouldRetry ) {
152- return shouldRetry ( error , this . retryAttempt ) ;
201+ if ( isRetryable ) {
202+ return isRetryable ( error , this . retryAttempt ) ;
153203 }
154204
155205 return false ;
@@ -172,10 +222,10 @@ export class TokenManager<T> {
172222 if ( this . shouldRetry ( error ) ) {
173223 this . retryAttempt ++ ;
174224 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 )
176226 this . scheduleNextRefresh ( retryDelay ) ;
177227 } else {
178- this . notifyError ( error , true ) ;
228+ this . notifyError ( error , false ) ;
179229 this . stop ( ) ;
180230 }
181231 }
@@ -255,13 +305,13 @@ export class TokenManager<T> {
255305 return this . currentToken ;
256306 }
257307
258- private notifyError = ( error : unknown , isFatal : boolean ) : void => {
308+ private notifyError ( error : unknown , isRetryable : boolean ) : void {
259309 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
260310
261311 if ( ! this . listener ) {
262312 throw new Error ( `TokenManager is not running but received an error: ${ errorMessage } ` ) ;
263313 }
264314
265- this . listener . onError ( new IDPError ( errorMessage , isFatal ) ) ;
315+ this . listener . onError ( new IDPError ( errorMessage , isRetryable ) ) ;
266316 }
267317}
0 commit comments