Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import utils from './utils';
- // First we need to make sure we are backwards compatible with IE (no ArrayBuffer.slice)
- if (!ArrayBuffer.prototype.slice) {
- ArrayBuffer.prototype.slice = function (begin, end) {
- let len = this.byteLength;
- begin = (begin|0) || 0;
- end = end === (void 0) ? len : (end|0);
- // Handle negative values.
- if (begin < 0) begin = Math.max(begin + len, 0);
- if (end < 0) end = Math.max(end + len, 0);
- if (len === 0 || begin >= len || begin >= end) {
- return new ArrayBuffer(0);
- }
- let length = Math.min(len - begin, end - begin);
- let target = new ArrayBuffer(length);
- let targetArray = new Uint8Array(target);
- targetArray.set(new Uint8Array(this, begin, length));
- return target;
- };
- }
- function getWebRequestExecutorFactory(appWebUrl, hostWebUrl, spLanguage) {
- let context = new window.SP.ClientContext(appWebUrl);
- const factory = new window.SP.ProxyWebRequestExecutorFactory(appWebUrl);
- context.set_webRequestExecutorFactory(factory);
- return context;
- }
- const jsomContext = () => {
- return getWebRequestExecutorFactory(utils.getSpContaxtUrlParams().appWebUrl, utils.getSpContaxtUrlParams().hostWebUrl, utils.getSpContaxtUrlParams().spLanguage);
- };
- const jsomAppContext = () => {
- let context = getWebRequestExecutorFactory(utils.getSpContaxtUrlParams().appWebUrl, utils.getSpContaxtUrlParams().hostWebUrl, utils.getSpContaxtUrlParams().spLanguage);
- // Use the host web URL to get a parent context - this allows us to get data from the parent
- let hostWebContext = new SP.AppContextSite(context, utils.getSpContaxtUrlParams().hostWebUrl);
- return hostWebContext;
- };
- // Base64 - this method converts the blob arrayBuffer into a binary string to send in the REST request
- function convertDataBinaryString(data) {
- let fileData = '';
- let byteArray = new Uint8Array(data);
- for (var i = 0; i < byteArray.byteLength; i++) {
- fileData += String.fromCharCode(byteArray[i]);
- }
- return fileData;
- }
- //this method sends the REST request using the SP RequestExecutor
- function executeAsync(endPointUrl, data, requestHeaders) {
- return new Promise((resolve, reject) => {
- // using a utils function we would get the APP WEB url value and pass it into the constructor...
- let executor = new SP.RequestExecutor(utils.getSpContaxtUrlParams().appWebUrl);
- // Send the request.
- executor.executeAsync({
- url: endPointUrl,
- method: "POST",
- body: data,
- binaryStringRequestBody: true,
- headers: requestHeaders,
- success: offset => resolve(offset),
- error: err => reject(err.responseText)
- });
- });
- }
- //this method sets up the REST request and then sends the chunk of file along with the unique indentifier (uploadId)
- function uploadFileChunk(id, libraryPath, fileName, chunk, data, byteOffset) {
- return new Promise((resolve, reject) => {
- let offset = chunk.offset === 0 ? '' : ',fileOffset=' + byteOffset;
- //parameterising the components of this endpoint avoids the max url length problem in SP (Querystring parameters are not included in this length)
- let endpoint = String.format("{0}/_api/sp.appcontextsite(@target)/web/getfilebyserverrelativeurl(@libraryPath)/{5}(uploadId=guid'{3}'{6})?@target='{4}'&@libraryPath='/sites/assetdatabase/{1}/{2}'",
- utils.getSpContaxtUrlParams().appWebUrl, libraryPath, fileName, id, utils.getSpContaxtUrlParams().hostWebUrl, chunk.method, offset);
- const headers = {
- "Accept": "application/json; odata=verbose",
- "Content-Type": "application/octet-stream"
- };
- executeAsync(endpoint, data, headers).then(offset => resolve(offset)).catch(err => reject(err.responseText));
- });
- }
- /*
- Calling this method is optional. If you have a need to show the progress, you can by updating an element in the DOM
- with a changes progress indicator to update after each chunk has uploaded (shows the percentage progress of the upload).
- */
- function setLoaderMessage(uploading, percentage) {
- let message = document.getElementsByClassName('loaderMessage');
- if (message !== null) {
- message[0].innerHTML = uploading ? `Uploading file<br />${percentage}% complete.` : 'Working on it...';
- }
- }
- //sometimes the file gets checked out by the REST method, so we need to check it back in when we are done.
- function checkinMajorVersion(libraryPath, fileName) {
- return new Promise((resolve, reject) => {
- let endpoint = String.format("{0}/_api/sp.appcontextsite(@target)/web/getfilebyserverrelativeurl(@libraryPath)/checkin(comment='File Added, initial commit.',checkintype=0)?@target='{3}'&@libraryPath='/sites/assetdatabase/{1}/{2}'",
- utils.getSpContaxtUrlParams().appWebUrl, libraryPath, fileName, utils.getSpContaxtUrlParams().hostWebUrl);
- const headers = {
- "Accept": "application/json; odata=verbose"
- };
- executeAsync(endpoint, '', headers).then(() => resolve(true)).catch(err => reject(err));
- });
- }
- //the final REST call is made to get the file information after it has been fully uploaded (especially the file list item id)
- function getFileInformation(libraryPath, fileName, resolve, reject) {
- let endpoint = String.format("{0}/_api/sp.appcontextsite(@target)/web/getfilebyserverrelativeurl(@libraryPath)/ListItemAllFields?@target='{3}'&@libraryPath='/sites/assetdatabase/{1}/{2}'",
- utils.getSpContaxtUrlParams().appWebUrl, libraryPath, fileName, utils.getSpContaxtUrlParams().hostWebUrl);
- const headers = {
- "Accept": "application/json; odata=verbose"
- };
- executeAsync(endpoint, '', headers).then(fileListItem => {
- console.log('fetching file information');
- const items = fileListItem.body ? fileListItem.body : fileListItem;
- const listItem = JSON.parse(items);
- //..and we are done.
- resolve(listItem.d);
- }).catch(err => {
- reject(err.responseText);
- });
- }
- //the primary method that resursively calls to get the chunks and upload them to the library (to make the complete file)
- function uploadFile(result, id, libraryPath, fileName, chunks, index, byteOffset, chunkPercentage, resolve, reject) {
- //we slice the file blob into the chunk we need to send in this request (byteOffset tells us the start position)
- const data = convertFileToBlobChunks(result, byteOffset, chunks[index]);
- if (byteOffset === 0) {
- //at the beginning of the upload set the message and starting percentage (0%)
- setLoaderMessage(true, 0);
- }
- //upload the chunk to the server using REST, using the unique upload guid as the identifier
- uploadFileChunk(id, libraryPath, fileName, chunks[index], data, byteOffset).then(
- value => {
- const isFinished = index === chunks.length - 1;
- if (!isFinished) {
- //the response value is a string of JSON (ugly) which we need to consume to find the offset
- const response = typeof value.body !== 'undefined' ? JSON.parse(value.body) : '';
- //depending on the position in the upload, the response string (JSON) can differ!
- if (typeof response.d.StartUpload !== 'undefined') {
- byteOffset = Number.parseInvariant(response.d.StartUpload);
- } else if (typeof response.d.ContinueUpload !== 'undefined') {
- byteOffset = Number.parseInvariant(response.d.ContinueUpload);
- }
- }
- index += 1;
- const percentageComplete = isFinished ? 100 : Math.round((index * chunkPercentage));
- // progress indication
- setLoaderMessage(true, percentageComplete);
- console.log(percentageComplete + '%');
- //More chunks to process before the file is finished, continue
- if (index < chunks.length) {
- uploadFile(result, id, libraryPath, fileName, chunks, index, byteOffset, chunkPercentage, resolve, reject);
- } else {
- setLoaderMessage(false);
- //check in the file and then resolve the file information back to the caller
- //checkinMajorVersion(libraryPath, fileName).then(() => {
- //when there was a checkin needed
- getFileInformation(libraryPath, fileName, resolve, reject);
- //}).catch(err => {
- //no checkin was necessary
- //getFileInformation(libraryPath, fileName, resolve, reject);
- //});
- }
- }
- ).catch(err => { console.log('Error in uploadFileChunk! '); window.Erz = err; });
- }
- //this is the initial method we call to create a dummy place holder file before overwriting it with the chunks of data...
- function createDummaryFile(ctx, fileName, libraryName) {
- return new Promise((resolve, reject) => {
- // Construct the endpoint - The GetList method is available for SharePoint Online only.
- let endpoint = String.format("{0}/_api/sp.appcontextsite(@target)/web/lists/getByTitle('{1}')/rootfolder/files/add(overwrite=true, url='{2}')?@target='{3}'",
- utils.getSpContaxtUrlParams().appWebUrl, libraryName, fileName, utils.getSpContaxtUrlParams().hostWebUrl);
- const headers = {
- "accept": "application/json;odata=verbose"
- };
- executeAsync(endpoint, convertDataBinaryString(2), headers).then(file => resolve(true)).catch(err => reject(err.responseText));
- });
- }
- //Helper method - depending on what chunk of data we are dealing with, we need to use the correct REST method...
- function getUploadMethod(offset, length, total) {
- if (offset + length + 1 > total) {
- return 'finishupload';
- } else if (offset === 0) {
- return 'startupload';
- } else if (offset < total) {
- return 'continueupload';
- }
- return null;
- }
- //this method slices the blob array buffer to the appropriate chunk and then calls off to get the BinaryString of that chunk
- function convertFileToBlobChunks(result, byteOffset, chunkInfo) {
- let arrayBuffer = chunkInfo.method === 'finishupload' ? result.slice(byteOffset) : result.slice(byteOffset, byteOffset + chunkInfo.length);
- return convertDataBinaryString(arrayBuffer);
- }
- module.exports = {
- upload: (file) => {
- return new Promise((resolve, reject) => {
- let ctx = jsomAppContext();
- // first we need to create a dummy file, before we can upload the file in chunks...
- createDummaryFile(ctx, file.name, 'Documents').then(result => {
- let fr = new FileReader();
- let offset = 0;
- // the total file size in bytes...
- let total = file.size;
- // 1MB Chunks as represented in bytes (if the file is less than a MB, seperate it into two chunks of 80% and 20% the size)...
- let length = 1000000 > total ? total * 0.8 : 1000000;
- let chunks = [];
- fr.onload = evt => {
- while (offset < total) {
- //if we are dealing with the final chunk, we need to know...
- if (offset + length > total) {
- length = total - offset;
- }
- //work out the chunks that need to be processed and the associated REST method (start, continue or finish)
- chunks.push({ offset, length, method: getUploadMethod(offset, length, total) });
- offset += length;
- }
- //each chunk is worth a percentage of the total size of the file...
- const chunkPercentage = parseFloat(((total / chunks.length) / total)) * 100;
- if (chunks.length > 0) {
- //the unique guid identifier to be used throughout the upload session
- const id = utils.getGuid();
- //Start the upload - send the data to SP
- uploadFile(evt.target.result, id, 'Documents', file.name, chunks, 0, 0, chunkPercentage, resolve, reject);
- }
- };
- //reads in the file using the fileReader HTML5 API (as an ArrayBuffer) - readAsBinaryString is not available in IE!
- fr.readAsArrayBuffer(file);
- });
- });
- }
- };
Add Comment
Please, Sign In to add comment