Skip to content
40 changes: 40 additions & 0 deletions spec/core/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
import { assert } from "chai";
import "isomorphic-fetch";

import { CustomAuthenticationProvider, TelemetryHandler } from "../../src";
import { Client } from "../../src/Client";
import { AuthProvider } from "../../src/IAuthProvider";
import { ClientOptions } from "../../src/IClientOptions";
import { Options } from "../../src/IOptions";
import { AuthenticationHandler } from "../../src/middleware/AuthenticationHandler";
import { ChaosHandler } from "../../src/middleware/ChaosHandler";
import { ChaosHandlerOptions } from "../../src/middleware/options/ChaosHandlerOptions";
import { ChaosStrategy } from "../../src/middleware/options/ChaosStrategy";
import { DummyAuthenticationProvider } from "../DummyAuthenticationProvider";
import { DummyHTTPMessageHandler } from "../DummyHTTPMessageHandler";

Expand Down Expand Up @@ -61,6 +66,41 @@ describe("Client.ts", () => {
assert.equal(error.name, "InvalidMiddlewareChain");
}
});

it("Init middleware using a middleware array", async () => {
const provider: AuthProvider = (done) => {
done(null, "dummy_token");
};
const authHandler = new AuthenticationHandler(new CustomAuthenticationProvider(provider));
const responseBody = "Test response body";
const options = new ChaosHandlerOptions(ChaosStrategy.MANUAL, "Testing middleware array", 200, 0, responseBody);
const middlewareArray = [authHandler, new ChaosHandler(options)];
const client = Client.initWithMiddleware({ middleware: middlewareArray });

const response = await client.api("me").get();
assert.equal(response, responseBody);
});

it("Init middleware using a chained middleware array", async () => {
const provider: AuthProvider = (done) => {
done(null, "dummy_token");
};
const authHandler = new AuthenticationHandler(new CustomAuthenticationProvider(provider));

const responseBody = "Test response body";
const options = new ChaosHandlerOptions(ChaosStrategy.MANUAL, "Testing chained middleware array", 200, 0, responseBody);
const chaosHandler = new ChaosHandler(options);
const telemetryHandler = new TelemetryHandler();

authHandler.setNext(telemetryHandler);
telemetryHandler.setNext(chaosHandler);

const middlewareArray = [authHandler];
const client = Client.initWithMiddleware({ middleware: middlewareArray });

const response = await client.api("me").get();
assert.equal(response, responseBody);
});
});

describe("init", () => {
Expand Down
31 changes: 31 additions & 0 deletions spec/core/HTTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,38 @@ describe("HTTPClient.ts", () => {
assert.isDefined(httpClient["middleware"]);
assert.equal(httpClient["middleware"], httpMessageHandler);
});

it("Should create an instance and populate middleware member when passing a middleware array", () => {
const client = new HTTPClient(...[httpMessageHandler]);
assert.isDefined(client["middleware"]);
assert.equal(client["middleware"], httpMessageHandler);
});

it("Should throw an error if middleware is undefined", () => {
try {
const client = new HTTPClient();
} catch (error) {
assert.equal(error.name, "InvalidMiddlewareChain");
}
});

it("Should throw an error if middleware is passed as null", () => {
try {
const client = new HTTPClient(null);
} catch (error) {
assert.equal(error.name, "InvalidMiddlewareChain");
}
});

it("Should throw an error if middleware is passed as an empty array", () => {
try {
const client = new HTTPClient(...[]);
} catch (error) {
assert.equal(error.name, "InvalidMiddlewareChain");
}
});
});

/* tslint:enable: no-string-literal */

describe("sendRequest", async () => {
Expand Down
27 changes: 27 additions & 0 deletions spec/middleware/MiddlewareFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/

import { assert } from "chai";

import { AuthenticationHandler, CustomAuthenticationProvider, HTTPMessageHandler, RedirectHandler, RetryHandler, TelemetryHandler } from "../../src";
import { AuthProvider } from "../../src/IAuthProvider";
import { MiddlewareFactory } from "../../src/middleware/MiddlewareFactory";

describe("MiddlewareFactory", () => {
it("Should return the default pipeline", () => {
const provider: AuthProvider = (done) => {
done(null, "dummy_token");
};
const defaultMiddleWareArray = MiddlewareFactory.getDefaultMiddlewareChain(new CustomAuthenticationProvider(provider));

assert.isTrue(defaultMiddleWareArray[0] instanceof AuthenticationHandler);
assert.isTrue(defaultMiddleWareArray[1] instanceof RetryHandler);
assert.isTrue(defaultMiddleWareArray[2] instanceof RedirectHandler);
assert.isTrue(defaultMiddleWareArray[3] instanceof TelemetryHandler);
assert.isTrue(defaultMiddleWareArray[4] instanceof HTTPMessageHandler);
});
});
2 changes: 1 addition & 1 deletion src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class Client {
} else if (clientOptions.authProvider !== undefined) {
httpClient = HTTPClientFactory.createWithAuthenticationProvider(clientOptions.authProvider);
} else if (clientOptions.middleware !== undefined) {
httpClient = new HTTPClient(clientOptions.middleware);
httpClient = new HTTPClient(...[].concat(clientOptions.middleware));
} else {
const error = new Error();
error.name = "InvalidMiddlewareChain";
Expand Down
42 changes: 39 additions & 3 deletions src/HTTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,46 @@ export class HTTPClient {
* @public
* @constructor
* Creates an instance of a HTTPClient
* @param {Middleware} middleware - The first middleware of the middleware chain
* @param {...Middleware} middleware - The first middleware of the middleware chain or a sequence of all the Middleware handlers
*/
public constructor(middleware: Middleware) {
this.middleware = middleware;
public constructor(...middleware: Middleware[]) {
if (!middleware || !middleware.length) {
const error = new Error();
error.name = "InvalidMiddlewareChain";
error.message = "Please provide a default middleware chain or custom middleware chain";
throw error;
}
this.setMiddleware(...middleware);
}

/**
* @private
* Processes the middleware parameter passed to set this.middleware property
* @param {...Middleware} middleware - The middleware passed
* @returns Nothing
*/
private setMiddleware(...middleware: Middleware[]): void {
if (middleware.length > 1) {
this.parseMiddleWareArray(middleware);
} else {
this.middleware = middleware[0];
}
}

/**
* @private
* Processes the middleware array to construct the chain
* and sets this.middleware property to the first middlware handler of the array
* @param {Middleware[]} middlewareArray - The array of middleware handlers
* @returns Nothing
*/
private parseMiddleWareArray(middlewareArray: Middleware[]) {
middlewareArray.forEach((element, index) => {
if (index < middlewareArray.length - 1) {
element.setNext(middlewareArray[index + 1]);
}
});
this.middleware = middlewareArray[0];
}

/**
Expand Down
9 changes: 5 additions & 4 deletions src/HTTPClientFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { TelemetryHandler } from "./middleware/TelemetryHandler";
* @returns A boolean representing the environment is node or not
*/
const isNodeEnvironment = (): boolean => {
return new Function("try {return this === global;}catch(e){return false;}")(); // tslint:disable-line: function-constructor
return typeof process === "object" && typeof require === "function";
};

/**
Expand Down Expand Up @@ -69,10 +69,11 @@ export class HTTPClientFactory {
* @public
* @static
* Creates a middleware chain with the given one
* @param {Middleware} middleware - The first middleware of the middleware chain
* @property {...Middleware} middleware - The first middleware of the middleware chain or a sequence of all the Middleware handlers
* @returns A HTTPClient instance
*/
public static createWithMiddleware(middleware: Middleware): HTTPClient {
return new HTTPClient(middleware);
public static createWithMiddleware(...middleware: Middleware[]): HTTPClient {
// Middleware should not empty or undefined. This is check is present in the HTTPClient constructor.
return new HTTPClient(...middleware);
}
}
4 changes: 2 additions & 2 deletions src/IClientOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { Middleware } from "./middleware/IMiddleware";
* @property {boolean} [debugLogging] - The boolean to enable/disable debug logging
* @property {string} [defaultVersion] - The default version that needs to be used while making graph api request
* @property {FetchOptions} [fetchOptions] - The options for fetch request
* @property {Middleware} [middleware] - The first middleware of the middleware chain
* @property {Middleware| Middleware[]} [middleware] - The first middleware of the middleware chain or an array of the Middleware handlers
*/
export interface ClientOptions {
authProvider?: AuthenticationProvider;
baseUrl?: string;
debugLogging?: boolean;
defaultVersion?: string;
fetchOptions?: FetchOptions;
middleware?: Middleware;
middleware?: Middleware | Middleware[];
}
6 changes: 5 additions & 1 deletion src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ export * from "../middleware/HTTPMessageHandler";
export * from "../middleware/IMiddleware";
export * from "../middleware/RetryHandler";
export * from "../middleware/TelemetryHandler";

export * from "../middleware/MiddlewareFactory";
export * from "../middleware/options/AuthenticationHandlerOptions";
export * from "../middleware/options/IMiddlewareOptions";
export * from "../middleware/options/RetryHandlerOptions";
export * from "../middleware/options/TelemetryHandlerOptions";
export * from "../middleware/options/ChaosHandlerOptions";
export * from "../middleware/options/ChaosStrategy";
export * from "../middleware/ChaosHandler";

export * from "../tasks/LargeFileUploadTask";
export * from "../tasks/OneDriveLargeFileUploadTask";
export * from "../tasks/PageIterator";

export * from "../Client";
export * from "../CustomAuthenticationProvider";
export * from "../GraphError";
export * from "../GraphRequest";
export * from "../IAuthProvider";
Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@ export * from "./middleware/IMiddleware";
export * from "./middleware/RetryHandler";
export * from "./middleware/RedirectHandler";
export * from "./middleware/TelemetryHandler";

export * from "./middleware/MiddlewareFactory";
export * from "./middleware/options/AuthenticationHandlerOptions";
export * from "./middleware/options/IMiddlewareOptions";
export * from "./middleware/options/RetryHandlerOptions";
export * from "./middleware/options/RedirectHandlerOptions";
export * from "./middleware/options/TelemetryHandlerOptions";
export * from "./middleware/options/ChaosHandlerOptions";
export * from "./middleware/options/ChaosStrategy";
export * from "./middleware/ChaosHandler";

export * from "./tasks/LargeFileUploadTask";
export * from "./tasks/OneDriveLargeFileUploadTask";
export * from "./tasks/PageIterator";

export * from "./Client";
export * from "./CustomAuthenticationProvider";
export * from "./GraphError";
export * from "./GraphRequest";
export * from "./IAuthProvider";
Expand Down
62 changes: 62 additions & 0 deletions src/middleware/MiddlewareFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/

/**
* @module MiddlewareFactory
*/

import { AuthenticationProvider } from "../IAuthenticationProvider";

import { AuthenticationHandler } from "./AuthenticationHandler";
import { HTTPMessageHandler } from "./HTTPMessageHandler";
import { Middleware } from "./IMiddleware";
import { RedirectHandlerOptions } from "./options/RedirectHandlerOptions";
import { RetryHandlerOptions } from "./options/RetryHandlerOptions";
import { RedirectHandler } from "./RedirectHandler";
import { RetryHandler } from "./RetryHandler";
import { TelemetryHandler } from "./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";
};

/**
* @class
* Class containing function(s) related to the middleware pipelines.
*/
export class MiddlewareFactory {
/**
* @public
* @static
* Returns the default middleware chain an array with the middleware handlers
* @param {AuthenticationProvider} authProvider - The authentication provider instance
* @returns an array of the middleware handlers of the default middleware chain
*/
public static getDefaultMiddlewareChain(authProvider: AuthenticationProvider): Middleware[] {
const middleware: Middleware[] = [];
const authenticationHandler = new AuthenticationHandler(authProvider);
const retryHandler = new RetryHandler(new RetryHandlerOptions());
const telemetryHandler = new TelemetryHandler();
const httpMessageHandler = new HTTPMessageHandler();

middleware.push(authenticationHandler);
middleware.push(retryHandler);
if (isNodeEnvironment()) {
const redirectHandler = new RedirectHandler(new RedirectHandlerOptions());
middleware.push(redirectHandler);
}
middleware.push(telemetryHandler);
middleware.push(httpMessageHandler);

return middleware;
}
}
5 changes: 4 additions & 1 deletion src/tasks/OneDriveLargeFileUploadTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export class OneDriveLargeFileUploadTask extends LargeFileUploadTask {
}
// we choose to encode each component of the file path separately because when encoding full URI
// with encodeURI, special characters like # or % in the file name doesn't get encoded as desired
return `/me/drive/root:${path.split('/').map(p => encodeURIComponent(p)).join('/')}${encodeURIComponent(fileName)}:/createUploadSession`;
return `/me/drive/root:${path
.split("/")
.map((p) => encodeURIComponent(p))
.join("/")}${encodeURIComponent(fileName)}:/createUploadSession`;
}

/**
Expand Down