From 34a5436e8f023848b018decb096a304b75d02af6 Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Sun, 21 Feb 2021 20:40:32 -0800 Subject: [PATCH 1/8] Adding upload result, isCancelled upload session property --- src/tasks/FileUploadUtil/UploadResult.ts | 31 +++++++++++ src/tasks/LargeFileUploadTask.ts | 68 ++++++++++++++++++++---- 2 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/tasks/FileUploadUtil/UploadResult.ts diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts new file mode 100644 index 000000000..27847a0f6 --- /dev/null +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -0,0 +1,31 @@ + +import { GraphResponseHandler } from "../../GraphResponseHandler"; +export class UploadResult { + private _location: string; + private _body: unknown; + private _uploadSuccess: boolean; + + public get location(): string { + return this._location; + } + + public set location(location: string) { + this._location = location; + } + + public get body() { + return this._body; + } + + public set body(body: unknown) { + // Body can be undefined. Empty body is expected. + this._body = body; + } + + public async setUploadResult(response: Response) { + this._location = response.headers.get("Location"); + this._body = await GraphResponseHandler.getResponse(response); + this._uploadSuccess = + } + +} \ No newline at end of file diff --git a/src/tasks/LargeFileUploadTask.ts b/src/tasks/LargeFileUploadTask.ts index 185390f9d..f6c47463d 100644 --- a/src/tasks/LargeFileUploadTask.ts +++ b/src/tasks/LargeFileUploadTask.ts @@ -9,8 +9,12 @@ * @module LargeFileUploadTask */ +import { GraphClientError } from "../GraphClientError"; import { Client } from "../index"; import { Range } from "../Range"; +import { ResponseType } from "../ResponseType"; +import { GraphResponseHandler } from "../GraphResponseHandler"; +import { UploadResult } from "./FileUploadUtil/UploadResult"; /** * @interface @@ -50,6 +54,7 @@ export interface LargeFileUploadTaskOptions { export interface LargeFileUploadSession { url: string; expiry: Date; + isCancelled?: boolean; } /** @@ -124,6 +129,7 @@ export class LargeFileUploadTask { const largeFileUploadSession: LargeFileUploadSession = { url: session.uploadUrl, expiry: new Date(session.expirationDateTime), + isCancelled: false }; return largeFileUploadSession; } @@ -215,8 +221,7 @@ export class LargeFileUploadTask { * @returns The promise resolves to uploaded response */ public async upload(): Promise { - // eslint-disable-next-line no-constant-condition - while (true) { + while (!this.uploadSession.isCancelled) { const nextRange = this.getNextRange(); if (nextRange.maxValue === -1) { const err = new Error("Task with which you are trying to upload is already completed, Please check for your uploaded file"); @@ -224,13 +229,27 @@ export class LargeFileUploadTask { throw err; } const fileSlice = this.sliceFile(nextRange); - const response = await this.uploadSlice(fileSlice, nextRange, this.file.size); - // Upon completion of upload process incase of onedrive, driveItem is returned, which contains id - if (response.id !== undefined) { - return response; - } else { - this.updateTaskStatus(response); + const rawResponse = await this.uploadSliceGetRawResponse(fileSlice, nextRange, this.file.size); + if (!rawResponse) { + throw new GraphClientError("Something went wrong! Large file upload slice response is null."); } + + const responseBody = await GraphResponseHandler.getResponse(rawResponse); + if (rawResponse.status == 201 || (rawResponse.status == 200 && responseBody.id)) { + const result = new UploadResult(); + return result.setUploadResult(rawResponse); + } + + /* Handling an API issue where the case of Outlook upload 'nextExpectedRanges' property is not uniform. + * https://github.com/microsoftgraph/msgraph-sdk-serviceissues/issues/39 + */ + const res: UploadStatusResponse = { + expirationDateTime: responseBody.expirationDateTime, + nextExpectedRanges: responseBody.NextExpectedRanges || responseBody.nextExpectedRanges, + }; + + this.updateTaskStatus(res); + this.updateTaskStatus(responseBody); } } @@ -251,6 +270,23 @@ export class LargeFileUploadTask { }) .put(fileSlice); } + /** + * @public + * @async + * Uploads given slice to the server + * @param {ArrayBuffer | Blob | File} fileSlice - The file slice + * @param {Range} range - The range value + * @param {number} totalSize - The total size of a complete file + */ + public async uploadSliceGetRawResponse(fileSlice: unknown, range: Range, totalSize: number): Promise { + return await this.client + .api(this.uploadSession.url) + .headers({ + "Content-Length": `${range.maxValue - range.minValue + 1}`, + "Content-Range": `bytes ${range.minValue}-${range.maxValue}/${totalSize}`, + }).responseType(ResponseType.RAW) + .put(fileSlice); + } /** * @public @@ -259,7 +295,11 @@ export class LargeFileUploadTask { * @returns The promise resolves to cancelled response */ public async cancel(): Promise { - return await this.client.api(this.uploadSession.url).delete(); + const deleteResponse = await this.client.api(this.uploadSession.url).responseType(ResponseType.RAW).delete() + if (deleteResponse.status && deleteResponse.status == 204) { + this.uploadSession.isCancelled = true; + } + return deleteResponse; } /** @@ -284,4 +324,14 @@ export class LargeFileUploadTask { await this.getStatus(); return await this.upload(); } + + /** + * @public + * @async + * Get the upload session information + * @returns The large file upload large file session + */ + public getUploadSession(): LargeFileUploadSession { + return this.uploadSession; + } } From b1492b4e8c383a95b7fb6488708677a28cfb1c70 Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Sun, 28 Feb 2021 14:33:37 -0800 Subject: [PATCH 2/8] AuthHandler check for graph url, createUploadResult static function --- .huskyrc | 3 - samples/node/main.js | 123 ++++++++++++++++++++--- samples/node/package-lock.json | 49 +++++++-- samples/node/package.json | 5 +- samples/node/secrets.sample.json | 3 - src/middleware/AuthenticationHandler.ts | 8 ++ src/tasks/FileUploadUtil/UploadResult.ts | 15 ++- src/tasks/LargeFileUploadTask.ts | 6 +- 8 files changed, 171 insertions(+), 41 deletions(-) delete mode 100644 samples/node/secrets.sample.json diff --git a/.huskyrc b/.huskyrc index d8d81fd78..2c63c0851 100644 --- a/.huskyrc +++ b/.huskyrc @@ -1,5 +1,2 @@ { - "hooks": { - "pre-commit": "npm run pre-build && git add src/Version.ts && lint-staged && npm run lint && npm run test" - } } diff --git a/samples/node/main.js b/samples/node/main.js index eddd2c1b9..542d0caf2 100644 --- a/samples/node/main.js +++ b/samples/node/main.js @@ -9,28 +9,119 @@ const MicrosoftGraph = require("../../lib/src/index.js"); const secrets = require("./secrets"); -const fs = require("fs"); +const fs = require('fs'); const client = MicrosoftGraph.Client.init({ - defaultVersion: "v1.0", - debugLogging: true, - authProvider: (done) => { - done(null, secrets.accessToken); - }, + defaultVersion: "v1.0", + debugLogging: true, + authProvider: (done) => { + done(null, secrets.accessToken); + }, }); +function uploadFile1() { + fs.readFile("./test.txt", {}, function(err, file) { + if (err) { + throw err; + } + let fileName = "text.txt"; + oneDriveLargeFileUpload1(client, file, fileName) + .then((response) => { + console.log(response); + console.log("File Uploaded Successfully.!!"); + }) + .catch((error) => { + throw error; + }); + }); +} +async function oneDriveLargeFileUpload1(client, file, fileName) { + try { + let options = { + path: "/Documents", + fileName, + rangeSize: 1024 * 1024, + }; + const uploadTask = await MicrosoftGraph.OneDriveLargeFileUploadTask.create(client, file, options); + const response = await uploadTask.upload(); + return response; + } catch (err) { + console.log(err); + } +} +uploadFile1(); + // Get the name of the authenticated user with promises -client - .api("/me") - .select("displayName") - .get() - .then((res) => { - console.log(res); - }) - .catch((err) => { - console.log(err); - }); +// client +// .api("/me") +// .select("displayName") +// .get() +// .then((res) => { +// console.log(res); +// }) +// .catch((err) => { +// console.log(err); +// }); + + +//uploadFile(); +function uploadFile() { + fs.readFile("./test.txt", {}, function (err, file) { + if (err) { + throw err; + } + var stats = fs.statSync("./test.txt") + + const messageId = "AAMkADZiNzhhNTVkLWU5MDEtNGNlNy1hMjZiLTJjN2RkNjcyNGM4NgBGAAAAAABxs3khvJ1fSYvq33QgqqSJBwBC_D0Xqz_3TKBt1JyxMQ_VAAAAAAEMAABC_D0Xqz_3TKBt1JyxMQ_VAACGMDZTAAA="; + client.api(`me/messages/${messageId}/attachments/createUploadSession`).post({ + AttachmentItem: { + attachmentType: 'file', + name: "test.txt", + size: stats.size, + } + }) + .then((response) => { + console.log(response); + + console.log("File Uploaded Successfully.!!"); + oneDriveLargeFileUpload(client, file, response) + .then((res) => { + console.log(res); + console.log("File Uploaded Successfully.!!"); + }) + .catch((error) => { + throw error; + }); + }) + .catch((error) => { + console.log(".!!"); + console.log(error); + }); + + }); +} +async function oneDriveLargeFileUpload(client, file, uploadSession) { + try { + let fileName = "test.txt"; + let options = { + path: "/Documents", + fileName, + rangeSize: 1024 * 1024, + }; + const s = { + url:uploadSession.uploadUrl, + expiry: uploadSession.expirationDateTime + } + console.log("uploadSession"); + const uploadTask = new MicrosoftGraph.LargeFileUploadTask(client, file, s, options); + console.log(uploadTask+"uploadTask"); + const response = await uploadTask.upload(); + return response; + } catch (err) { + console.log(err); + } +} /* // Update the authenticated users birthday. diff --git a/samples/node/package-lock.json b/samples/node/package-lock.json index f5a72dd8f..a1d7e74b4 100644 --- a/samples/node/package-lock.json +++ b/samples/node/package-lock.json @@ -4,12 +4,17 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.24" + "iconv-lite": "~0.4.13" } }, "iconv-lite": { @@ -17,9 +22,14 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -30,24 +40,51 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "3.0.0" + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + }, "whatwg-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", diff --git a/samples/node/package.json b/samples/node/package.json index 94a1baa9e..30a8bf867 100644 --- a/samples/node/package.json +++ b/samples/node/package.json @@ -9,6 +9,9 @@ "author": "", "license": "ISC", "dependencies": { - "isomorphic-fetch": "^2.2.1" + "bytes": "^3.1.0", + "isomorphic-fetch": "^2.2.1", + "minimist": "^1.2.5", + "path": "^0.12.7" } } diff --git a/samples/node/secrets.sample.json b/samples/node/secrets.sample.json deleted file mode 100644 index 3dd6aa16c..000000000 --- a/samples/node/secrets.sample.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "accessToken": "" -} diff --git a/src/middleware/AuthenticationHandler.ts b/src/middleware/AuthenticationHandler.ts index 121c5e0b1..e5e735333 100644 --- a/src/middleware/AuthenticationHandler.ts +++ b/src/middleware/AuthenticationHandler.ts @@ -9,6 +9,7 @@ * @module AuthenticationHandler */ +import { isGraphURL } from "../GraphRequestUtil"; import { AuthenticationProvider } from "../IAuthenticationProvider"; import { AuthenticationProviderOptions } from "../IAuthenticationProviderOptions"; import { Context } from "../IContext"; @@ -60,6 +61,8 @@ export class AuthenticationHandler implements Middleware { * @returns A Promise that resolves to nothing */ public async execute(context: Context): Promise { + const url = typeof context.request === "string" ? context.request : context.request.url; + if (isGraphURL(url)) { let options: AuthenticationHandlerOptions; if (context.middlewareControl instanceof MiddlewareControl) { options = context.middlewareControl.getMiddlewareOptions(AuthenticationHandlerOptions) as AuthenticationHandlerOptions; @@ -77,6 +80,11 @@ export class AuthenticationHandler implements Middleware { const bearerKey = `Bearer ${token}`; appendRequestHeader(context.request, context.options, AuthenticationHandler.AUTHORIZATION_HEADER, bearerKey); TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED); + } else { + if (context.options.headers) { + delete context.options.headers[AuthenticationHandler.AUTHORIZATION_HEADER]; + } + } return await this.nextMiddleware.execute(context); } diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts index 27847a0f6..874382d76 100644 --- a/src/tasks/FileUploadUtil/UploadResult.ts +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -1,9 +1,6 @@ - -import { GraphResponseHandler } from "../../GraphResponseHandler"; export class UploadResult { private _location: string; private _body: unknown; - private _uploadSuccess: boolean; public get location(): string { return this._location; @@ -22,10 +19,12 @@ export class UploadResult { this._body = body; } - public async setUploadResult(response: Response) { - this._location = response.headers.get("Location"); - this._body = await GraphResponseHandler.getResponse(response); - this._uploadSuccess = + public constructor(body:unknown, location:string){ + this._location = location; + this._body = body; + } + + public static CreateUploadResult(responsebody?:unknown, responseHeaders?:Headers){ + return new UploadResult(responsebody, (responseHeaders.get("Location") || responseHeaders.get("headers"))); } - } \ No newline at end of file diff --git a/src/tasks/LargeFileUploadTask.ts b/src/tasks/LargeFileUploadTask.ts index f6c47463d..10eb99cec 100644 --- a/src/tasks/LargeFileUploadTask.ts +++ b/src/tasks/LargeFileUploadTask.ts @@ -236,11 +236,10 @@ export class LargeFileUploadTask { const responseBody = await GraphResponseHandler.getResponse(rawResponse); if (rawResponse.status == 201 || (rawResponse.status == 200 && responseBody.id)) { - const result = new UploadResult(); - return result.setUploadResult(rawResponse); + return UploadResult.CreateUploadResult(responseBody, rawResponse.headers); } - /* Handling an API issue where the case of Outlook upload 'nextExpectedRanges' property is not uniform. + /* Handling the API issue where the case of Outlook upload response property -'nextExpectedRanges' is not uniform. * https://github.com/microsoftgraph/msgraph-sdk-serviceissues/issues/39 */ const res: UploadStatusResponse = { @@ -249,7 +248,6 @@ export class LargeFileUploadTask { }; this.updateTaskStatus(res); - this.updateTaskStatus(responseBody); } } From 5602d6e3d5a9954e36987b8727dff4582351b58c Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Sun, 28 Feb 2021 20:15:52 -0800 Subject: [PATCH 3/8] Adding unit test, linting --- .huskyrc | 3 + package.json | 3 +- samples/node/main.js | 123 +++-------------------- samples/node/package.json | 5 +- src/middleware/AuthenticationHandler.ts | 44 ++++---- src/tasks/FileUploadUtil/UploadResult.ts | 105 +++++++++++++------ src/tasks/LargeFileUploadTask.ts | 32 ++++-- test/common/tasks/LargeFileUploadTask.ts | 58 ++++++++++- 8 files changed, 198 insertions(+), 175 deletions(-) diff --git a/.huskyrc b/.huskyrc index 2c63c0851..d8d81fd78 100644 --- a/.huskyrc +++ b/.huskyrc @@ -1,2 +1,5 @@ { + "hooks": { + "pre-commit": "npm run pre-build && git add src/Version.ts && lint-staged && npm run lint && npm run test" + } } diff --git a/package.json b/package.json index ebf33aa4b..64d31c11e 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,8 @@ "test:coverage": "TS_NODE_PROJECT='./tsconfig-cjs.json' nyc mocha --require isomorphic-fetch -r ts-node/register test/common/**/*.ts && mocha --require isomorphic-fetch -r ts-node/register test/common/**/*.ts", "test:cjs": "npm run build:sub_cjs && mocha 'lib/test/common/**/*.js' --require isomorphic-fetch && mocha 'lib/test/node/**/*.js' --require isomorphic-fetch", "test:esm": "npm run build:sub_es && mocha 'lib/es/test/common/**/*.js' --require esm --require isomorphic-fetch && mocha 'lib/es/test/node/**/*.js' --require esm --require isomorphic-fetch", - "karma": "karma start --single-run --browsers ChromeHeadless karma.conf.js" + "karma": "karma start --single-run --browsers ChromeHeadless karma.conf.js", + "test:l": "mocha 'lib/test/common/**/*.js' --require isomorphic-fetch -grep LargeFileUploadTask.ts" }, "repository": { "type": "git", diff --git a/samples/node/main.js b/samples/node/main.js index 542d0caf2..eddd2c1b9 100644 --- a/samples/node/main.js +++ b/samples/node/main.js @@ -9,119 +9,28 @@ const MicrosoftGraph = require("../../lib/src/index.js"); const secrets = require("./secrets"); -const fs = require('fs'); +const fs = require("fs"); const client = MicrosoftGraph.Client.init({ - defaultVersion: "v1.0", - debugLogging: true, - authProvider: (done) => { - done(null, secrets.accessToken); - }, + defaultVersion: "v1.0", + debugLogging: true, + authProvider: (done) => { + done(null, secrets.accessToken); + }, }); -function uploadFile1() { - fs.readFile("./test.txt", {}, function(err, file) { - if (err) { - throw err; - } - let fileName = "text.txt"; - oneDriveLargeFileUpload1(client, file, fileName) - .then((response) => { - console.log(response); - console.log("File Uploaded Successfully.!!"); - }) - .catch((error) => { - throw error; - }); - }); -} -async function oneDriveLargeFileUpload1(client, file, fileName) { - try { - let options = { - path: "/Documents", - fileName, - rangeSize: 1024 * 1024, - }; - const uploadTask = await MicrosoftGraph.OneDriveLargeFileUploadTask.create(client, file, options); - const response = await uploadTask.upload(); - return response; - } catch (err) { - console.log(err); - } -} -uploadFile1(); - // Get the name of the authenticated user with promises -// client -// .api("/me") -// .select("displayName") -// .get() -// .then((res) => { -// console.log(res); -// }) -// .catch((err) => { -// console.log(err); -// }); - - -//uploadFile(); -function uploadFile() { - fs.readFile("./test.txt", {}, function (err, file) { - if (err) { - throw err; - } - var stats = fs.statSync("./test.txt") - - const messageId = "AAMkADZiNzhhNTVkLWU5MDEtNGNlNy1hMjZiLTJjN2RkNjcyNGM4NgBGAAAAAABxs3khvJ1fSYvq33QgqqSJBwBC_D0Xqz_3TKBt1JyxMQ_VAAAAAAEMAABC_D0Xqz_3TKBt1JyxMQ_VAACGMDZTAAA="; - client.api(`me/messages/${messageId}/attachments/createUploadSession`).post({ - AttachmentItem: { - attachmentType: 'file', - name: "test.txt", - size: stats.size, - } - }) - .then((response) => { - console.log(response); - - console.log("File Uploaded Successfully.!!"); - oneDriveLargeFileUpload(client, file, response) - .then((res) => { - console.log(res); - console.log("File Uploaded Successfully.!!"); - }) - .catch((error) => { - throw error; - }); - }) - .catch((error) => { - console.log(".!!"); - console.log(error); - }); - - }); -} +client + .api("/me") + .select("displayName") + .get() + .then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); -async function oneDriveLargeFileUpload(client, file, uploadSession) { - try { - let fileName = "test.txt"; - let options = { - path: "/Documents", - fileName, - rangeSize: 1024 * 1024, - }; - const s = { - url:uploadSession.uploadUrl, - expiry: uploadSession.expirationDateTime - } - console.log("uploadSession"); - const uploadTask = new MicrosoftGraph.LargeFileUploadTask(client, file, s, options); - console.log(uploadTask+"uploadTask"); - const response = await uploadTask.upload(); - return response; - } catch (err) { - console.log(err); - } -} /* // Update the authenticated users birthday. diff --git a/samples/node/package.json b/samples/node/package.json index 30a8bf867..94a1baa9e 100644 --- a/samples/node/package.json +++ b/samples/node/package.json @@ -9,9 +9,6 @@ "author": "", "license": "ISC", "dependencies": { - "bytes": "^3.1.0", - "isomorphic-fetch": "^2.2.1", - "minimist": "^1.2.5", - "path": "^0.12.7" + "isomorphic-fetch": "^2.2.1" } } diff --git a/src/middleware/AuthenticationHandler.ts b/src/middleware/AuthenticationHandler.ts index e5e735333..f84346975 100644 --- a/src/middleware/AuthenticationHandler.ts +++ b/src/middleware/AuthenticationHandler.ts @@ -62,29 +62,29 @@ export class AuthenticationHandler implements Middleware { */ public async execute(context: Context): Promise { const url = typeof context.request === "string" ? context.request : context.request.url; - if (isGraphURL(url)) { - let options: AuthenticationHandlerOptions; - if (context.middlewareControl instanceof MiddlewareControl) { - options = context.middlewareControl.getMiddlewareOptions(AuthenticationHandlerOptions) as AuthenticationHandlerOptions; + if (isGraphURL(url)) { + let options: AuthenticationHandlerOptions; + if (context.middlewareControl instanceof MiddlewareControl) { + options = context.middlewareControl.getMiddlewareOptions(AuthenticationHandlerOptions) as AuthenticationHandlerOptions; + } + let authenticationProvider: AuthenticationProvider; + let authenticationProviderOptions: AuthenticationProviderOptions; + if (typeof options !== "undefined") { + authenticationProvider = options.authenticationProvider; + authenticationProviderOptions = options.authenticationProviderOptions; + } + if (typeof authenticationProvider === "undefined") { + authenticationProvider = this.authenticationProvider; + } + const token: string = await authenticationProvider.getAccessToken(authenticationProviderOptions); + const bearerKey = `Bearer ${token}`; + appendRequestHeader(context.request, context.options, AuthenticationHandler.AUTHORIZATION_HEADER, bearerKey); + TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED); + } else { + if (context.options.headers) { + delete context.options.headers[AuthenticationHandler.AUTHORIZATION_HEADER]; + } } - let authenticationProvider: AuthenticationProvider; - let authenticationProviderOptions: AuthenticationProviderOptions; - if (typeof options !== "undefined") { - authenticationProvider = options.authenticationProvider; - authenticationProviderOptions = options.authenticationProviderOptions; - } - if (typeof authenticationProvider === "undefined") { - authenticationProvider = this.authenticationProvider; - } - const token: string = await authenticationProvider.getAccessToken(authenticationProviderOptions); - const bearerKey = `Bearer ${token}`; - appendRequestHeader(context.request, context.options, AuthenticationHandler.AUTHORIZATION_HEADER, bearerKey); - TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED); - } else { - if (context.options.headers) { - delete context.options.headers[AuthenticationHandler.AUTHORIZATION_HEADER]; - } - } return await this.nextMiddleware.execute(context); } diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts index 874382d76..b65c14669 100644 --- a/src/tasks/FileUploadUtil/UploadResult.ts +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -1,30 +1,77 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +/** + * Class representing a successful file upload result + */ export class UploadResult { - private _location: string; - private _body: unknown; - - public get location(): string { - return this._location; - } - - public set location(location: string) { - this._location = location; - } - - public get body() { - return this._body; - } - - public set body(body: unknown) { - // Body can be undefined. Empty body is expected. - this._body = body; - } - - public constructor(body:unknown, location:string){ - this._location = location; - this._body = body; - } - - public static CreateUploadResult(responsebody?:unknown, responseHeaders?:Headers){ - return new UploadResult(responsebody, (responseHeaders.get("Location") || responseHeaders.get("headers"))); - } -} \ No newline at end of file + /** + * @private + * Location value looked up in the response header + */ + private _location: string; + + /** + * @private + * Response body of the final raw response + */ + private _responseBody: unknown; + + /** + * @public + * Get of the location value. + * Location value is looked up in the response header + */ + public get location(): string { + return this._location; + } + + /** + * @public + * Set the location value + * Location value is looked up in the response header + */ + public set location(location: string) { + this._location = location; + } + + /** + * @public + * Get The response body from the completed upload response + */ + public get responseBody() { + return this._responseBody; + } + + /** + * @public + * Set the response body from the completed upload response + */ + public set responseBody(responseBody: unknown) { + this._responseBody = responseBody; + } + + /** + * @public + * @param {responsebody} responsebody - The response body from the completed upload response + * @param {responsebody} location - The location value from the headers from the completed upload response + */ + public constructor(responseBody: unknown, location: string) { + // Response body or the location parameter can be undefined. + this._location = location; + this._responseBody = responseBody; + } + + /** + * @public + * @param {responsebody} responsebody - The response body from the completed upload response + * @param {responsebody} responseHeaders - The headers from the completed upload response + */ + public static CreateUploadResult(responsebody?: unknown, responseHeaders?: Headers) { + return new UploadResult(responsebody, responseHeaders.get("location")); + } +} diff --git a/src/tasks/LargeFileUploadTask.ts b/src/tasks/LargeFileUploadTask.ts index 10eb99cec..8b37e8fa6 100644 --- a/src/tasks/LargeFileUploadTask.ts +++ b/src/tasks/LargeFileUploadTask.ts @@ -10,10 +10,10 @@ */ import { GraphClientError } from "../GraphClientError"; +import { GraphResponseHandler } from "../GraphResponseHandler"; import { Client } from "../index"; import { Range } from "../Range"; import { ResponseType } from "../ResponseType"; -import { GraphResponseHandler } from "../GraphResponseHandler"; import { UploadResult } from "./FileUploadUtil/UploadResult"; /** @@ -129,7 +129,7 @@ export class LargeFileUploadTask { const largeFileUploadSession: LargeFileUploadSession = { url: session.uploadUrl, expiry: new Date(session.expirationDateTime), - isCancelled: false + isCancelled: false, }; return largeFileUploadSession; } @@ -235,13 +235,17 @@ export class LargeFileUploadTask { } const responseBody = await GraphResponseHandler.getResponse(rawResponse); - if (rawResponse.status == 201 || (rawResponse.status == 200 && responseBody.id)) { + /** + * (rawResponse.status === 201) -> This condition is applicable for OneDrive, PrintDocument and Outlook APIs. + * (rawResponse.status === 200 && responseBody.id) -> This additional condition is applicable only for OneDrive API. + */ + if (rawResponse.status === 201 || (rawResponse.status === 200 && responseBody.id)) { return UploadResult.CreateUploadResult(responseBody, rawResponse.headers); } /* Handling the API issue where the case of Outlook upload response property -'nextExpectedRanges' is not uniform. - * https://github.com/microsoftgraph/msgraph-sdk-serviceissues/issues/39 - */ + * https://github.com/microsoftgraph/msgraph-sdk-serviceissues/issues/39 + */ const res: UploadStatusResponse = { expirationDateTime: responseBody.expirationDateTime, nextExpectedRanges: responseBody.NextExpectedRanges || responseBody.nextExpectedRanges, @@ -258,6 +262,7 @@ export class LargeFileUploadTask { * @param {ArrayBuffer | Blob | File} fileSlice - The file slice * @param {Range} range - The range value * @param {number} totalSize - The total size of a complete file + * @returns The response body of the upload slice result */ public async uploadSlice(fileSlice: ArrayBuffer | Blob | File, range: Range, totalSize: number): Promise { return await this.client @@ -272,9 +277,10 @@ export class LargeFileUploadTask { * @public * @async * Uploads given slice to the server - * @param {ArrayBuffer | Blob | File} fileSlice - The file slice + * @param {unknown} fileSlice - The file slice * @param {Range} range - The range value * @param {number} totalSize - The total size of a complete file + * @returns The raw response of the upload slice result */ public async uploadSliceGetRawResponse(fileSlice: unknown, range: Range, totalSize: number): Promise { return await this.client @@ -282,7 +288,8 @@ export class LargeFileUploadTask { .headers({ "Content-Length": `${range.maxValue - range.minValue + 1}`, "Content-Range": `bytes ${range.minValue}-${range.maxValue}/${totalSize}`, - }).responseType(ResponseType.RAW) + }) + .responseType(ResponseType.RAW) .put(fileSlice); } @@ -293,11 +300,14 @@ export class LargeFileUploadTask { * @returns The promise resolves to cancelled response */ public async cancel(): Promise { - const deleteResponse = await this.client.api(this.uploadSession.url).responseType(ResponseType.RAW).delete() - if (deleteResponse.status && deleteResponse.status == 204) { + const cancelResponse = await this.client + .api(this.uploadSession.url) + .responseType(ResponseType.RAW) + .delete(); + if (cancelResponse.status === 204) { this.uploadSession.isCancelled = true; } - return deleteResponse; + return cancelResponse; } /** @@ -327,7 +337,7 @@ export class LargeFileUploadTask { * @public * @async * Get the upload session information - * @returns The large file upload large file session + * @returns The large file upload session */ public getUploadSession(): LargeFileUploadSession { return this.uploadSession; diff --git a/test/common/tasks/LargeFileUploadTask.ts b/test/common/tasks/LargeFileUploadTask.ts index 62fceefbe..edf45857a 100644 --- a/test/common/tasks/LargeFileUploadTask.ts +++ b/test/common/tasks/LargeFileUploadTask.ts @@ -6,7 +6,9 @@ */ import { assert } from "chai"; +import * as sinon from "sinon"; +import { UploadResult } from "../../../src/tasks/FileUploadUtil/UploadResult"; import { LargeFileUploadTask } from "../../../src/tasks/LargeFileUploadTask"; import { getClient } from "../../test-helper"; @@ -157,12 +159,66 @@ describe("LargeFileUploadTask.ts", () => { const options = { rangeSize: 327680, }; - const uploadTask = new LargeFileUploadTask(getClient(), fileObj, uploadSession, options); + + it("Should return a Upload Result object after a completed task with 201 status", async () => { + const location = "TEST_URL"; + const body = { + id: "TEST_ID", + }; + const uploadTask = new LargeFileUploadTask(getClient(), fileObj, uploadSession, options); + const status201 = { + status: 200, + stautsText: "OK", + headers: { + "Content-Type": "application/json", + location, + }, + }; + const rawResponse = new Response(JSON.stringify(body), status201); + + const moq = sinon.mock(uploadTask); + moq.expects("uploadSliceGetRawResponse").resolves(rawResponse); + const result = await uploadTask.upload(); + assert.isDefined(result); + assert.instanceOf(result, UploadResult); + assert.equal(result["location"], location); + const responseBody = result["responseBody"]; + assert.isDefined(responseBody); + assert.equal(responseBody["id"], "TEST_ID"); + }); + + it("Should return a Upload Result object after a completed task with 200 status and id", async () => { + const location = "TEST_URL"; + const body = { + id: "TEST_ID", + }; + const uploadTask = new LargeFileUploadTask(getClient(), fileObj, uploadSession, options); + const status200 = { + status: 200, + stautsText: "OK", + headers: { + "Content-Type": "application/json", + location, + }, + }; + const rawResponse = new Response(JSON.stringify(body), status200); + + const moq = sinon.mock(uploadTask); + moq.expects("uploadSliceGetRawResponse").resolves(rawResponse); + const result = await uploadTask.upload(); + assert.isDefined(result); + assert.instanceOf(result, UploadResult); + assert.equal(result["location"], location); + const responseBody = result["responseBody"]; + assert.isDefined(responseBody); + assert.equal(responseBody["id"], "TEST_ID"); + }); it("Should return an exception while trying to upload the file upload completed task", (done) => { const statusResponse = { expirationDateTime: "2018-08-06T09:05:45.195Z", nextExpectedRanges: [], }; + const uploadTask = new LargeFileUploadTask(getClient(), fileObj, uploadSession, options); uploadTask["updateTaskStatus"](statusResponse); uploadTask .upload() From 6c26af3940787252c9fd3089a9784e12ba3cf055 Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Sun, 28 Feb 2021 23:46:39 -0800 Subject: [PATCH 4/8] resetting files --- package.json | 3 +- samples/node/package-lock.json | 49 ++++---------------------------- samples/node/secrets.sample.json | 3 ++ 3 files changed, 10 insertions(+), 45 deletions(-) create mode 100644 samples/node/secrets.sample.json diff --git a/package.json b/package.json index 64d31c11e..ebf33aa4b 100644 --- a/package.json +++ b/package.json @@ -92,8 +92,7 @@ "test:coverage": "TS_NODE_PROJECT='./tsconfig-cjs.json' nyc mocha --require isomorphic-fetch -r ts-node/register test/common/**/*.ts && mocha --require isomorphic-fetch -r ts-node/register test/common/**/*.ts", "test:cjs": "npm run build:sub_cjs && mocha 'lib/test/common/**/*.js' --require isomorphic-fetch && mocha 'lib/test/node/**/*.js' --require isomorphic-fetch", "test:esm": "npm run build:sub_es && mocha 'lib/es/test/common/**/*.js' --require esm --require isomorphic-fetch && mocha 'lib/es/test/node/**/*.js' --require esm --require isomorphic-fetch", - "karma": "karma start --single-run --browsers ChromeHeadless karma.conf.js", - "test:l": "mocha 'lib/test/common/**/*.js' --require isomorphic-fetch -grep LargeFileUploadTask.ts" + "karma": "karma start --single-run --browsers ChromeHeadless karma.conf.js" }, "repository": { "type": "git", diff --git a/samples/node/package-lock.json b/samples/node/package-lock.json index a1d7e74b4..f5a72dd8f 100644 --- a/samples/node/package-lock.json +++ b/samples/node/package-lock.json @@ -4,17 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "0.4.24" } }, "iconv-lite": { @@ -22,14 +17,9 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -40,51 +30,24 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" + "node-fetch": "1.7.3", + "whatwg-fetch": "3.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "requires": { - "process": "^0.11.1", - "util": "^0.10.3" + "encoding": "0.1.12", + "is-stream": "1.1.0" } }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - } - }, "whatwg-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", diff --git a/samples/node/secrets.sample.json b/samples/node/secrets.sample.json new file mode 100644 index 000000000..3dd6aa16c --- /dev/null +++ b/samples/node/secrets.sample.json @@ -0,0 +1,3 @@ +{ + "accessToken": "" +} From a14eafc46276051c20cdc4e7629d164033e11020 Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Mon, 1 Mar 2021 23:17:56 -0800 Subject: [PATCH 5/8] cancel task doc, conditions update --- docs/tasks/LargeFileUploadTask.md | 16 ++++++++++++++++ src/middleware/AuthenticationHandler.ts | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/tasks/LargeFileUploadTask.md b/docs/tasks/LargeFileUploadTask.md index 981eff3f7..f0e2d401f 100644 --- a/docs/tasks/LargeFileUploadTask.md +++ b/docs/tasks/LargeFileUploadTask.md @@ -129,3 +129,19 @@ async function largeFileUpload(client, file) { } } ``` + +## Cancelling a largeFileUpload task + +_Cancelling an upload session sends a DELETE request to the upload session URL_ + +```typescript +const cancelResponse = await uploadTask.cancel(); +``` + +## Get the largeFileUpload session + +_Returns the largeFileUpload session information containing the URL, expiry date and cancellation status of the task_ + +```typescript +const uploadsession: LargeFileUploadSession = uploadTask.getUploadSession(); +``` diff --git a/src/middleware/AuthenticationHandler.ts b/src/middleware/AuthenticationHandler.ts index f84346975..267f8543f 100644 --- a/src/middleware/AuthenticationHandler.ts +++ b/src/middleware/AuthenticationHandler.ts @@ -69,11 +69,11 @@ export class AuthenticationHandler implements Middleware { } let authenticationProvider: AuthenticationProvider; let authenticationProviderOptions: AuthenticationProviderOptions; - if (typeof options !== "undefined") { + if (options) { authenticationProvider = options.authenticationProvider; authenticationProviderOptions = options.authenticationProviderOptions; } - if (typeof authenticationProvider === "undefined") { + if (!authenticationProvider) { authenticationProvider = this.authenticationProvider; } const token: string = await authenticationProvider.getAccessToken(authenticationProviderOptions); From 05e3d36abd077e244a5984ddd56ff007f8f45c7a Mon Sep 17 00:00:00 2001 From: Nikitha Chettiar Date: Thu, 4 Mar 2021 08:49:18 -0800 Subject: [PATCH 6/8] Correcting param case --- src/tasks/FileUploadUtil/UploadResult.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts index b65c14669..0332e196d 100644 --- a/src/tasks/FileUploadUtil/UploadResult.ts +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -68,10 +68,10 @@ export class UploadResult { /** * @public - * @param {responsebody} responsebody - The response body from the completed upload response + * @param {responsebody} responseBody - The response body from the completed upload response * @param {responsebody} responseHeaders - The headers from the completed upload response */ - public static CreateUploadResult(responsebody?: unknown, responseHeaders?: Headers) { - return new UploadResult(responsebody, responseHeaders.get("location")); + public static CreateUploadResult(responseBody?: unknown, responseHeaders?: Headers) { + return new UploadResult(responseBody, responseHeaders.get("location")); } } From a18b0a2768c73867ff2bfadfbb37a3479422f8df Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 4 Mar 2021 12:46:22 -0500 Subject: [PATCH 7/8] Apply suggestions from code review --- src/tasks/FileUploadUtil/UploadResult.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts index 0332e196d..6920ac43c 100644 --- a/src/tasks/FileUploadUtil/UploadResult.ts +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -68,8 +68,8 @@ export class UploadResult { /** * @public - * @param {responsebody} responseBody - The response body from the completed upload response - * @param {responsebody} responseHeaders - The headers from the completed upload response + * @param {responseBody} responseBody - The response body from the completed upload response + * @param {responseHeaders} responseHeaders - The headers from the completed upload response */ public static CreateUploadResult(responseBody?: unknown, responseHeaders?: Headers) { return new UploadResult(responseBody, responseHeaders.get("location")); From dcf1d6ccd76c34aa7020bab4fe9795972fbb68ff Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 4 Mar 2021 12:47:35 -0500 Subject: [PATCH 8/8] Apply suggestions from code review --- src/tasks/FileUploadUtil/UploadResult.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/FileUploadUtil/UploadResult.ts b/src/tasks/FileUploadUtil/UploadResult.ts index 6920ac43c..e2d88dd02 100644 --- a/src/tasks/FileUploadUtil/UploadResult.ts +++ b/src/tasks/FileUploadUtil/UploadResult.ts @@ -57,8 +57,8 @@ export class UploadResult { /** * @public - * @param {responsebody} responsebody - The response body from the completed upload response - * @param {responsebody} location - The location value from the headers from the completed upload response + * @param {responseBody} responsebody - The response body from the completed upload response + * @param {location} location - The location value from the headers from the completed upload response */ public constructor(responseBody: unknown, location: string) { // Response body or the location parameter can be undefined.