@microsoft/msgraph-sdk-core
Version:
Core functionalities for the Microsoft Graph JavaScript SDK
232 lines • 10.3 kB
JavaScript
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/
/**
* @module LargeFileUploadTask
**/
import { RequestInformation, HttpMethod, } from "@microsoft/kiota-abstractions";
import { UploadSlice } from "./UploadSlice.js";
import { SeekableStreamReader } from "./SeekableStreamReader.js";
/**
* UploadSession ParsableFactory
* Creates a factory function to deserialize the upload session response.
*
* @param {ParseNode} _parseNode - The parse node to deserialize.
* @returns {Function} - A function that takes an instance of Parsable and returns a record of deserialization functions.
*/
export const createUploadSessionFromDiscriminatorValue = (_parseNode) => {
return deserializeIntoUploadSession;
};
/**
* Deserializes the upload session response body.
*
* @param {Partial<UploadSession>} [uploadSession] - The upload session object to deserialize into.
* @returns {Record<string, (node: ParseNode) => void>} - A record of deserialization functions.
*/
export const deserializeIntoUploadSession = (uploadSession = {}) => {
return {
expirationDateTime: n => {
uploadSession.expirationDateTime = n.getDateValue();
},
nextExpectedRanges: n => {
uploadSession.nextExpectedRanges = n.getCollectionOfPrimitiveValues();
},
"@odata.type": n => {
uploadSession.odataType = n.getStringValue();
},
uploadUrl: n => {
uploadSession.uploadUrl = n.getStringValue();
},
};
};
/**
* @constant
* A default slice size for a large file
*/
const DefaultSliceSize = 320 * 1024;
/**
* A class representing LargeFileUploadTask
*/
export class LargeFileUploadTask {
/**
* Constructs a new instance of the LargeFileUploadTask class.
*
* @param {RequestAdapter} requestAdapter - The request adapter to use for making HTTP requests.
* @param {Parsable} uploadSession - The upload session information.
* @param {ReadableStream<Uint8Array>} uploadStream - Returns an instance of an unconsumed new stream to be uploaded.
* @param {number} [maxSliceSize=-1] - The maximum size of each file slice to be uploaded.
* @param {ParsableFactory<T>} parsableFactory - The factory to create parsable objects.
* @param {ErrorMappings} [errorMappings] - error mappings.
*
* @throws {Error} If any of the required parameters are undefined or invalid.
*/
constructor(requestAdapter, uploadSession, uploadStream, maxSliceSize = -1, parsableFactory, errorMappings) {
this.requestAdapter = requestAdapter;
this.maxSliceSize = maxSliceSize;
this.parsableFactory = parsableFactory;
/**
* @private
* The ranges to upload
*/
this.rangesRemaining = [];
if (!uploadSession) {
const error = new Error("Upload session is undefined, Please provide a valid upload session");
error.name = "Invalid Upload Session Error";
throw error;
}
if (!uploadStream) {
const error = new Error("Upload stream is undefined, Please provide a valid upload stream");
error.name = "Invalid Upload Stream Error";
throw error;
}
if (!requestAdapter) {
const error = new Error("Request adapter is undefined, Please provide a valid request adapter");
error.name = "Invalid Request Adapter Error";
throw error;
}
if (!parsableFactory) {
const error = new Error("Parsable factory is undefined, Please provide a valid parsable factory");
error.name = "Invalid Parsable Factory Error";
throw error;
}
if (uploadStream.locked) {
const error = new Error("Upload stream is locked, Please provide a valid upload stream");
error.name = "Invalid Upload Stream Error";
throw error;
}
if (maxSliceSize <= 0) {
this.maxSliceSize = DefaultSliceSize;
}
this.parsableFactory = parsableFactory;
this.seekableStreamReader = new SeekableStreamReader(uploadStream);
this.Session = this.extractSessionInfo(uploadSession);
this.rangesRemaining = this.getRangesRemaining(this.Session);
this.errorMappings = errorMappings;
}
/**
* Uploads the file in a sequential order by slicing the file in terms of ranges.
*
* @param {IProgress} [progress] - Optional progress receiver to report upload progress.
* @returns {Promise<UploadResult<T>>} - The result of the upload.
* @throws {Error} If the upload fails.
*/
async upload(progress) {
const uploadUrl = this.Session.uploadUrl;
if (!uploadUrl) {
throw new Error("Upload URL is a required parameter.");
}
for (const range of this.rangesRemaining) {
let currentRangeBegin = range[0];
while (currentRangeBegin <= range[1]) {
const nextSliceSize = this.nextSliceSize(currentRangeBegin, range[1]);
const uploadRequest = new UploadSlice(this.requestAdapter, uploadUrl, currentRangeBegin, currentRangeBegin + nextSliceSize - 1, range[1] + 1, this.parsableFactory, this.errorMappings, this.seekableStreamReader);
const uploadResult = await uploadRequest.uploadSlice();
progress === null || progress === void 0 ? void 0 : progress.report(uploadRequest.rangeEnd);
const { itemResponse, location } = uploadResult;
if (itemResponse || location) {
return uploadResult;
}
currentRangeBegin += nextSliceSize;
}
}
throw new Error("Upload failed");
}
/**
* @public
* Resumes the current upload session
* @param progress
*/
async resume(progress) {
await this.updateSession();
return this.upload(progress);
}
/**
* Refreshes the current upload session status by making a GET request to the upload URL.
* Updates the session expiration date, next expected ranges, and remaining ranges based on the response.
*
* @returns {Promise<UploadSession | undefined>} - A promise that resolves to the updated upload session.
* @throws {Error} If the request fails.
*/
async updateSession() {
const url = this.Session.uploadUrl;
if (!url) {
throw new Error("Upload url is invalid");
}
const requestInformation = new RequestInformation(HttpMethod.GET, url);
const response = await this.requestAdapter.send(requestInformation, createUploadSessionFromDiscriminatorValue, this.errorMappings);
if (response) {
this.Session.expirationDateTime = response.expirationDateTime;
this.Session.nextExpectedRanges = response.nextExpectedRanges;
if (response.uploadUrl) {
this.Session.uploadUrl = response.uploadUrl;
}
this.rangesRemaining = this.getRangesRemaining(this.Session);
}
return response;
}
/**
* Deletes the current upload session.
* Sends a PUT request to the upload URL to cancel the session.
*
* @returns {Promise<void>} A promise that resolves when the session is canceled.
*/
async deleteSession() {
const url = this.Session.uploadUrl;
if (!url) {
throw new Error("Upload url is invalid");
}
const requestInformation = new RequestInformation(HttpMethod.DELETE, url);
await this.requestAdapter.sendNoResponseContent(requestInformation, this.errorMappings);
}
/**
* Extracts the upload session information from a parsable object.
*
* @param {Parsable} parsable - The parsable object containing the upload session information.
* @returns {UploadSession} - The extracted upload session information.
*/
extractSessionInfo(parsable) {
const { expirationDateTime, nextExpectedRanges, odataType, uploadUrl } = parsable;
if (!nextExpectedRanges || !uploadUrl || nextExpectedRanges.length === 0) {
throw new Error("Upload session is invalid");
}
return {
expirationDateTime: expirationDateTime !== null && expirationDateTime !== void 0 ? expirationDateTime : null,
nextExpectedRanges: nextExpectedRanges !== null && nextExpectedRanges !== void 0 ? nextExpectedRanges : null,
odataType: odataType !== null && odataType !== void 0 ? odataType : null,
uploadUrl: uploadUrl !== null && uploadUrl !== void 0 ? uploadUrl : null,
};
}
/**
* Calculates the size of the next slice to be uploaded.
*
* @param {number} currentRangeBegin - The beginning of the current range.
* @param {number} currentRangeEnd - The end of the current range.
* @returns {number} - The size of the next slice.
*/
nextSliceSize(currentRangeBegin, currentRangeEnd) {
const sizeBasedOnRange = currentRangeEnd - currentRangeBegin + 1;
return sizeBasedOnRange > this.maxSliceSize ? this.maxSliceSize : sizeBasedOnRange;
}
/**
* @private
* Parses the upload session response and returns a nested number array of ranges pending upload
* @param uploadSession
*/
getRangesRemaining(uploadSession) {
// nextExpectedRanges: https://dev.onedrive.com/items/upload_large_files.htm
// Sample: ["12345-55232","77829-99375"]
// Also, second number in range can be blank, which means 'until the end'
const ranges = [];
if (uploadSession.nextExpectedRanges) {
ranges.push(...uploadSession.nextExpectedRanges.map(rangeString => {
const rangeArray = rangeString.split("-");
return [parseInt(rangeArray[0], 10), parseInt(rangeArray[1], 10)];
}));
}
return ranges;
}
}
//# sourceMappingURL=LargeFileUploadTask.js.map