diff --git a/docs/CustomMiddlewareChain.md b/docs/CustomMiddlewareChain.md index 6615bc7eb..f4d48b319 100644 --- a/docs/CustomMiddlewareChain.md +++ b/docs/CustomMiddlewareChain.md @@ -18,7 +18,7 @@ import { Middleware } from "@microsoft/microsoft-graph-client"; import { Context } from "@microsoft/microsoft-graph-client"; export class MyLoggingHandler implements Middleware { - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; public async execute(context: Context): Promise { try { @@ -29,7 +29,7 @@ export class MyLoggingHandler implements Middleware { url = context.request.url; } console.log(url); - return await this.nextMiddleware.execute(context); + return await this.nextMiddleware?.execute(context); } catch (error) { throw error; } @@ -117,7 +117,7 @@ import { Middleware } from "@microsoft/microsoft-graph-client"; import { Context } from "@microsoft/microsoft-graph-client"; export class MyLoggingHandler implements Middleware { - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; public async execute(context: Context): Promise { try { @@ -132,7 +132,7 @@ export class MyLoggingHandler implements Middleware { } else { console.log(url); } - await this.nextMiddleware.execute(context); + await this.nextMiddleware?.execute(context); } catch (error) { throw error; } diff --git a/src/HTTPClientFactory.ts b/src/HTTPClientFactory.ts index 1b01b9fad..ecb778435 100644 --- a/src/HTTPClientFactory.ts +++ b/src/HTTPClientFactory.ts @@ -11,23 +11,8 @@ import { HTTPClient } from "./HTTPClient"; import { AuthenticationProvider } from "./IAuthenticationProvider"; -import { AuthenticationHandler } from "./middleware/AuthenticationHandler"; -import { HTTPMessageHandler } from "./middleware/HTTPMessageHandler"; import { Middleware } from "./middleware/IMiddleware"; -import { RedirectHandlerOptions } from "./middleware/options/RedirectHandlerOptions"; -import { RetryHandlerOptions } from "./middleware/options/RetryHandlerOptions"; -import { RedirectHandler } from "./middleware/RedirectHandler"; -import { RetryHandler } from "./middleware/RetryHandler"; -import { TelemetryHandler } from "./middleware/TelemetryHandler"; - -/** - * @private - * To check whether the environment is node or not - * @returns A boolean representing the environment is node or not - */ -const isNodeEnvironment = (): boolean => { - return typeof process === "object" && typeof require === "function"; -}; +import { MiddlewareFactory } from "./middleware/MiddlewareFactory"; /** * @class @@ -48,21 +33,17 @@ export class HTTPClientFactory { * them will remain same. For example, Retry and Redirect handlers might be working multiple times for a request based on the response but their auth token would remain same. */ public static createWithAuthenticationProvider(authProvider: AuthenticationProvider): HTTPClient { - const authenticationHandler = new AuthenticationHandler(authProvider); - const retryHandler = new RetryHandler(new RetryHandlerOptions()); - const telemetryHandler = new TelemetryHandler(); - const httpMessageHandler = new HTTPMessageHandler(); + const middlewareChain = MiddlewareFactory.getDefaultMiddlewareChain(authProvider); - authenticationHandler.setNext(retryHandler); - if (isNodeEnvironment()) { - const redirectHandler = new RedirectHandler(new RedirectHandlerOptions()); - retryHandler.setNext(redirectHandler); - redirectHandler.setNext(telemetryHandler); - } else { - retryHandler.setNext(telemetryHandler); + for (let i = 0; i < middlewareChain.length - 1; i++) { + const current = middlewareChain[i]; + const next = middlewareChain[i + 1]; + current.setNext?.(next); } - telemetryHandler.setNext(httpMessageHandler); - return HTTPClientFactory.createWithMiddleware(authenticationHandler); + + const chainHead = middlewareChain[0]; + + return HTTPClientFactory.createWithMiddleware(chainHead); } /** diff --git a/src/middleware/AuthenticationHandler.ts b/src/middleware/AuthenticationHandler.ts index 3e25f7cdf..a4e1d490d 100644 --- a/src/middleware/AuthenticationHandler.ts +++ b/src/middleware/AuthenticationHandler.ts @@ -41,7 +41,7 @@ export class AuthenticationHandler implements Middleware { * @private * A member to hold next middleware in the middleware chain */ - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; /** * @public @@ -85,6 +85,7 @@ export class AuthenticationHandler implements Middleware { delete context.options.headers[AuthenticationHandler.AUTHORIZATION_HEADER]; } } + if (!this.nextMiddleware) return; return await this.nextMiddleware.execute(context); } diff --git a/src/middleware/ChaosHandler.ts b/src/middleware/ChaosHandler.ts index 8f5d6073b..e2e91b6af 100644 --- a/src/middleware/ChaosHandler.ts +++ b/src/middleware/ChaosHandler.ts @@ -43,7 +43,7 @@ export class ChaosHandler implements Middleware { * @private * A member to hold next middleware in the middleware chain */ - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; /** * @public diff --git a/src/middleware/HTTPMessageHandler.ts b/src/middleware/HTTPMessageHandler.ts index cc6f50416..ac7b70f62 100644 --- a/src/middleware/HTTPMessageHandler.ts +++ b/src/middleware/HTTPMessageHandler.ts @@ -18,6 +18,12 @@ import { Middleware } from "./IMiddleware"; * Class for HTTPMessageHandler */ export class HTTPMessageHandler implements Middleware { + /** + * @private + * A member to hold next middleware in the middleware chain + */ + private nextMiddleware?: Middleware; + /** * @public * @async @@ -27,5 +33,17 @@ export class HTTPMessageHandler implements Middleware { */ public async execute(context: Context): Promise { context.response = await fetch(context.request, context.options); + if (!this.nextMiddleware) return; + return await this.nextMiddleware.execute(context); + } + + /** + * @public + * To set the next middleware in the chain + * @param {Middleware} next - The middleware instance + * @returns Nothing + */ + public setNext(next: Middleware): void { + this.nextMiddleware = next; } } diff --git a/src/middleware/IMiddleware.ts b/src/middleware/IMiddleware.ts index 70dacc703..b47f22910 100644 --- a/src/middleware/IMiddleware.ts +++ b/src/middleware/IMiddleware.ts @@ -14,5 +14,5 @@ import { Context } from "../IContext"; */ export interface Middleware { execute: (context: Context) => Promise; - setNext?: (middleware: Middleware) => void; + setNext: (middleware: Middleware) => void; } diff --git a/src/middleware/RedirectHandler.ts b/src/middleware/RedirectHandler.ts index 911e408f2..4181d4db1 100644 --- a/src/middleware/RedirectHandler.ts +++ b/src/middleware/RedirectHandler.ts @@ -75,7 +75,7 @@ export class RedirectHandler implements Middleware { * @private * A member to hold next middleware in the middleware chain */ - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; /** * @public @@ -190,7 +190,7 @@ export class RedirectHandler implements Middleware { * @returns A promise that resolves to nothing */ private async executeWithRedirect(context: Context, redirectCount: number, options: RedirectHandlerOptions): Promise { - await this.nextMiddleware.execute(context); + await this.nextMiddleware?.execute(context); const response = context.response; if (redirectCount < options.maxRedirects && this.isRedirect(response) && this.hasLocationHeader(response) && options.shouldRedirect(response)) { ++redirectCount; diff --git a/src/middleware/RetryHandler.ts b/src/middleware/RetryHandler.ts index 36836fecb..9ad7e4c22 100644 --- a/src/middleware/RetryHandler.ts +++ b/src/middleware/RetryHandler.ts @@ -53,7 +53,7 @@ export class RetryHandler implements Middleware { * @private * A member to hold next middleware in the middleware chain */ - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; /** * @private @@ -170,7 +170,10 @@ export class RetryHandler implements Middleware { * @returns A Promise that resolves to nothing */ private async executeWithRetry(context: Context, retryAttempts: number, options: RetryHandlerOptions): Promise { - await this.nextMiddleware.execute(context); + // The execute method ensures the nextMiddleware will exist. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await this.nextMiddleware!.execute(context); + if (retryAttempts < options.maxRetries && this.isRetry(context.response) && this.isBuffered(context.request, context.options) && options.shouldRetry(options.delay, retryAttempts, context.request, context.options, context.response)) { ++retryAttempts; setRequestHeader(context.request, context.options, RetryHandler.RETRY_ATTEMPT_HEADER, retryAttempts.toString()); @@ -190,6 +193,8 @@ export class RetryHandler implements Middleware { * @returns A Promise that resolves to nothing */ public async execute(context: Context): Promise { + if (!this.nextMiddleware) return; + const retryAttempts = 0; const options: RetryHandlerOptions = this.getOptions(context); TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.RETRY_HANDLER_ENABLED); diff --git a/src/middleware/TelemetryHandler.ts b/src/middleware/TelemetryHandler.ts index 77278437c..ba7b059d1 100644 --- a/src/middleware/TelemetryHandler.ts +++ b/src/middleware/TelemetryHandler.ts @@ -54,7 +54,7 @@ export class TelemetryHandler implements Middleware { * @private * A member to hold next middleware in the middleware chain */ - private nextMiddleware: Middleware; + private nextMiddleware?: Middleware; /** * @public @@ -88,6 +88,7 @@ export class TelemetryHandler implements Middleware { delete context.options.headers[TelemetryHandler.CLIENT_REQUEST_ID_HEADER]; delete context.options.headers[TelemetryHandler.SDK_VERSION_HEADER]; } + if (!this.nextMiddleware) return; return await this.nextMiddleware.execute(context); } diff --git a/test/DummyHTTPMessageHandler.ts b/test/DummyHTTPMessageHandler.ts index 68a3cded7..25987afbd 100644 --- a/test/DummyHTTPMessageHandler.ts +++ b/test/DummyHTTPMessageHandler.ts @@ -24,6 +24,12 @@ export class DummyHTTPMessageHandler implements Middleware { */ private responses: Response[]; + /** + * @private + * A member to hold next middleware in the middleware chain + */ + private nextMiddleware?: Middleware; + /** * @public * @constructor @@ -54,6 +60,11 @@ export class DummyHTTPMessageHandler implements Middleware { */ public async execute(context: Context) { context.response = this.responses.shift(); - return; + if (!this.nextMiddleware) return; + return await this.nextMiddleware.execute(context); + } + + public setNext(middleware: Middleware) { + this.nextMiddleware = middleware; } }