Skip to content

Commit 9ad1150

Browse files
authored
LargeFileUpload - node js stream support and progress handling, rollup library updates (#401)
* Adding fileobject classes * making slice function async, testing stream upload * Adding IProgress interface, sort package.json * conflict behaviour param, onedrivetask with file object instance * Test update with fileobj, slice range value * adding stream upload test * Adding progresshandler complete failed unit tests * Updating documentation, largefiletask dev test * more docs, little clean up, restore file * package.json sorting * readme correction * Change ProgressHandler name, introducing generics, const usage * Folder naming and rearrangement * Update doc note * resetting publishing doc, publishing index * workload test update * Defend null in create onedrive, return types
1 parent 398303d commit 9ad1150

File tree

20 files changed

+1775
-670
lines changed

20 files changed

+1775
-670
lines changed

design/publishing.md

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,77 @@
1-
##### This design document focuses on the following -
1+
##### This design document focuses on the following -
2+
23
1. Separate entry points for node and browser.
34
2. Specifying the browser field in package.json.
45
3. Changes in the bundling process.
56

67
##### Terms -
78

8-
* bundler - Module bundlers are tools frontend developers used to bundle JavaScript modules into a single JavaScript files that can be executed in the browser.
9+
- bundler - Module bundlers are tools frontend developers used to bundle JavaScript modules into a single JavaScript files that can be executed in the browser.
10+
11+
- rollup - rollup.js is the module bundler that the JS SDK uses.
912

10-
* rollup - rollup.js is the module bundler that the JS SDK uses.
13+
- package.json fields -
1114

12-
* package.json fields -
13-
* main - The main field is a module ID that is the primary entry point to your program. Points to the CJS modules.
15+
- main - The main field is a module ID that is the primary entry point to your program. Points to the CJS modules.
1416

15-
* module - The module field is not an official npm feature but a common convention among bundlers to designate how to import an ESM version of a library. Points to the ES modules.
17+
- module - The module field is not an official npm feature but a common convention among bundlers to designate how to import an ESM version of a library. Points to the ES modules.
1618

17-
* browser - If the module is meant to be used client-side, the browser field should be used instead of the main field.
19+
- browser - If the module is meant to be used client-side, the browser field should be used instead of the main field.
1820

1921
##### Current set up -
2022

21-
1.
22-
TypeScript Source Code
23+
1. TypeScript Source Code
2324
/ \
24-
Transpiles into JavaScript
25+
Transpiles into JavaScript
2526
'lib' folder
2627
/ \
2728
CJS module ES modules
28-
2. main - `lib/src/index.js`
29-
module - `lib/es/src/index.js`
29+
2. main - `lib/src/index.js` module - `lib/es/src/index.js`
30+
31+
3. Rollup bundling output
32+
33+
- `graph-js-sdk.js` - Bundled and minified file in IIFE format. This file can be directly used in the browser with a `<script>` tag.
34+
- `graph-es-sdk.js` - Bundled file in ES format.
3035

31-
3. Rollup bundling output
32-
* `graph-js-sdk.js` - Bundled and minified file in IIFE format. This file can be directly used in the browser with a `<script>` tag.
33-
* `graph-es-sdk.js` - Bundled file in ES format.
3436
4. Entry point for rollup - `lib/es/src/browser/index.js`.
3537

3638
##### Difference between src/index.js and src/browser/index.js
37-
1. `src/browser/index.js` does not export `RedirectHandler` and `RedirectHandlerOptions`. Redirection is handled by the browser.
39+
40+
1. `src/browser/index.js` does not export `RedirectHandler` and `RedirectHandlerOptions`. Redirection is handled by the browser.
3841
2. `src/browser/index.js` exports `src/browser/ImplicitMsalProvider`.
39-
3. `src/browser/ImplicitMsalProvider` does not import or require 'msal' dependency. While,
40-
`src/ImplicitMsalProvider` imports or requires 'msal' in the implementation.
42+
3. `src/browser/ImplicitMsalProvider` does not import or require 'msal' dependency. While, `src/ImplicitMsalProvider` imports or requires 'msal' in the implementation.
4143
4. My assumtion is that `src/browser/ImplicitMsalProvider` is implemented specifically for the rollup process and to skip the rollup external dependency while using `graph-js-sdk.js` in the browser.
4244

4345
Note - Browser applications using the ES modules from the npm package of the JS SDK refer to the `module` entry point - `lib/es/src/index.js`(not the browser entry point). Example - Graph Explorer.
4446

45-
##### Upcoming changes -
47+
##### Upcoming changes -
4648

4749
1. Use the browser field for the following -
4850

49-
* The Graph JS SDK currently has two entry files, `src/index` and `src/browser/index`.
50-
Use the browser field to indicate the browser entry point.
51-
Example -
51+
- The Graph JS SDK currently has two entry files, `src/index` and `src/browser/index`. Use the browser field to indicate the browser entry point. Example -
52+
5253
```json
5354
"browser":
5455
{ "lib/es/src/index.js": "lib/es/src/browser/index.js" }
5556
```
57+
5658
Currently, the main and "module field in the package.json. This will remain the same.
57-
* Better way to handle environment specific implementation. For example, using the `browser` field as follows -
58-
"browser":
59+
60+
- Better way to handle environment specific implementation. For example, using the `browser` field as follows - "browser":
61+
5962
```json
60-
{
61-
"stream": "stream-browserify",
62-
"Feature-Node.js": "Feature-Browser.js"
63-
}
63+
{
64+
"stream": "stream-browserify",
65+
"Feature-Node.js": "Feature-Browser.js"
66+
}
6467
```
6568

66-
2. Remove export of `src/browser/ImplicitMsalProvider` from `src/browser/index`.
67-
* Till `ImplicitMsalProvider` is maintained in the repo, maintain a separate entry point say `rollup-index` for the rollup process which exports `src/browser/index` and `src/browser/ImplicitMsalProvider`.
68-
* Continue rolling up the `src/browser/ImplicitMsalProvider` as it is currently done and not introduce breaking changes here as it is going to be deprecated.
69-
* Remove the separate entry point once `ImplicitMsalProvider` is removed and use the browser entry point for roll up thereafter. The goal is to maintain a consistent entry point or usage for browser applications using the JS SDK and the rollup/bundling process.
69+
2. Remove export of `src/browser/ImplicitMsalProvider` from `src/browser/index`.
70+
71+
- Till `ImplicitMsalProvider` is maintained in the repo, maintain a separate entry point say `rollup-index` for the rollup process which exports `src/browser/index` and `src/browser/ImplicitMsalProvider`.
72+
- Continue rolling up the `src/browser/ImplicitMsalProvider` as it is currently done and not introduce breaking changes here as it is going to be deprecated.
73+
- Remove the separate entry point once `ImplicitMsalProvider` is removed and use the browser entry point for roll up thereafter. The goal is to maintain a consistent entry point or usage for browser applications using the JS SDK and the rollup/bundling process.
7074

7175
3. Bundle the authproviders separately as they are optional.
7276

73-
4. Stop bundling in ES format, that is remove `graph-es-sdk.js` as the ES modules are being shipped.
77+
4. Stop bundling in ES format, that is remove `graph-es-sdk.js` as the ES modules are being shipped.

docs/TokenCredentialAuthenticationProvider.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ The browser use of the file is as follows:
6161

6262
```html
6363
<!-- include the script -->
64-
<script type="text/javascript" src="<PATH_TO_SCRIPT>/graph-client-tokenCredentialAuthProvider.js"></script>;
65-
// create an authProvider
66-
var authProvider = new MicrosoftGraph.TokenCredentialAuthProvider.TokenCredentialAuthenticationProvider(tokenCred, { scopes: scopes });
67-
68-
client = MicrosoftGraph.Client.initWithMiddleware({
69-
authProvider: authProvider,
70-
});
64+
<script type="text/javascript" src="<PATH_TO_SCRIPT>/graph-client-tokenCredentialAuthProvider.js"></script>
65+
; // create an authProvider var authProvider = new MicrosoftGraph.TokenCredentialAuthProvider.TokenCredentialAuthenticationProvider(tokenCred, { scopes: scopes }); client = MicrosoftGraph.Client.initWithMiddleware({ authProvider: authProvider, });
7166
```

docs/tasks/LargeFileUploadTask.md

Lines changed: 134 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,159 @@
1-
# Large File Upload Task - Uploading large files to OneDrive
1+
# Large File Upload Task - Uploading large files to OneDrive, Outlook, Print API.
22

3-
This task simplifies the implementation of OneDrive's [resumable upload](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/driveitem_createuploadsession).
3+
References -
4+
5+
- [Outlook's large file attachment](https://docs.microsoft.com/en-us/graph/outlook-large-attachments)
6+
- [OneDrive's resumable upload](https://docs.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0&preserve-view=true)
7+
- [Print API's large file upload](https://docs.microsoft.com/en-us/graph/upload-data-to-upload-session)
48

59
## Creating the client instance
610

711
Refer [this documentation](../CreatingClientInstance.md) for initializing the client.
812

9-
## Uploading from browser
13+
## Using the LargeFileUpload Task
14+
15+
#### Create an upload session
16+
17+
First step for any upload task is the creation of the upload session.
1018

11-
HTML to select the file for uploading.
19+
**Example of a payload for Outlook**
1220

13-
```HTML
14-
<input id="fileUpload" type="file" onchange="fileUpload(this)" />
21+
```typescript
22+
const payload = {
23+
AttachmentItem: {
24+
attachmentType: "file",
25+
name: "<FILE_NAME>",
26+
size: FILE_SIZE,
27+
},
28+
};
1529
```
1630

17-
Get files from the input element and start uploading.
31+
**Example of a payload for OneDrive**
1832

1933
```typescript
20-
async function fileUpload(elem) {
21-
let file = elem.files[0];
22-
try {
23-
let response = await largeFileUpload(client, file, file.name);
24-
console.log(response);
25-
console.log("File Uploaded Successfully.!!");
26-
} catch (error) {
27-
console.error(error);
28-
}
29-
}
34+
const payload = {
35+
item: {
36+
"@microsoft.graph.conflictBehavior": "rename",
37+
name: "<FILE_NAME>",
38+
},
39+
};
40+
```
3041

31-
async function largeFileUpload(client, file) {
32-
try {
33-
let options = {
34-
path: "/Documents",
35-
fileName: file.name,
36-
rangeSize: 1024 * 1024,
37-
};
38-
const uploadTask = await MicrosoftGraph.OneDriveLargeFileUploadTask.create(client, file, options);
39-
const response = await uploadTask.upload();
40-
return response;
41-
} catch (err) {
42-
throw err;
43-
}
44-
}
42+
**Create the upload session**
43+
44+
```typescript
45+
const uploadSession = LargeFileUploadTask.createUploadSession(client, "REQUEST_URL", payload);
4546
```
4647

47-
## Uploading from NodeJS
48+
#### Creating the LargeFileUploadTask object
49+
50+
- To create the LargeFileUploadTask object you need to create -
51+
- An upload session as shown above.
52+
- A `FileObject` instance.
53+
54+
**FileObject Interface**
4855

4956
```typescript
50-
function uploadFile() {
51-
fs.readFile("<PATH_OF_THE_FILE>", {}, function(err, file) {
52-
if (err) {
53-
throw err;
54-
}
55-
let fileName = "<NAME_OF_THE_FILE_WITH_EXTN>";
56-
oneDriveLargeFileUpload(client, file, fileName)
57-
.then((response) => {
58-
console.log(response);
59-
console.log("File Uploaded Successfully.!!");
60-
})
61-
.catch((error) => {
62-
throw err;
63-
});
64-
});
57+
export interface FileObject<T> {
58+
content: T;
59+
name: string;
60+
size: number;
61+
sliceFile(range: Range): Promise<ArrayBuffer | Blob | Buffer>;
6562
}
63+
```
64+
65+
The Microsoft Graph JavaScript Client SDK provides two implementions -
66+
67+
1. StreamUpload - Supports Node.js stream upload
68+
69+
```typescript
70+
import StreamUpload from "@microsoft/microsoft-graph-client";
71+
import * as fs from "fs";
72+
73+
const fileName = "<FILE_NAME>";
74+
const stats = fs.statSync(`./test/sample_files/${fileName}`);
75+
const totalsize = stats.size;
76+
const readStream = fs.createReadStream(`./test/sample_files/${fileName}`);
77+
const fileObject = new StreamUpload(readStream, fileName, totalsize);
78+
```
79+
80+
Note - In case of a browser application, you can use [stream-browserify](https://www.npmjs.com/package/stream-browserify) and [buffer](https://www.npmjs.com/package/buffer).
81+
82+
2. FileUpload - Supports upload of file formats - ArrayBuffer, Blob, Buffer
83+
84+
```typescript
85+
import FileUpload from "@microsoft/microsoft-graph-client";
86+
import * as fs from "fs";
87+
88+
const fileName = "<FILE_NAME>";
89+
const stats = fs.statSync(`./test/sample_files/${fileName}`);
90+
const totalsize = stats.size;
91+
const readStream = fs.readFileSync(`./test/sample_files/${fileName}`);
92+
const fileObject = new FileUpload(readStream, fileName, totalsize);
93+
```
6694

67-
async function oneDriveLargeFileUpload(client, file, fileName) {
68-
try {
69-
let options = {
70-
path: "/Documents",
71-
fileName,
72-
rangeSize: 1024 * 1024,
73-
};
74-
const uploadTask = await OneDriveLargeFileUploadTask.create(client, file, options);
75-
const response = await uploadTask.upload();
76-
return response;
77-
} catch (err) {
78-
console.log(err);
79-
}
95+
**_Note_** - You can also have a customized `FileObject` implementation which contains the `sliceFile(range: Range)` function which implements the logic to split the file into ranges.
96+
97+
**Initiate the LargefileUploadTask options with Progress Handler and Range Size**
98+
99+
```typescript
100+
const progress = (range?: Range, extraCallBackParams?: unknown) => {
101+
// Handle progress event
102+
};
103+
104+
const progressCallBack: Progress = {
105+
progress,
106+
extraCallBackParam, // additional parameters to the callback
107+
};
108+
109+
const options: LargeFileUploadTaskOptions = {
110+
rangeSize: 327680,
111+
progressCallBack: progressCallBack,
112+
};
113+
```
114+
115+
**Create a LargefileUploadTask object**
116+
117+
```typescript
118+
const uploadTask = new LargeFileUploadTask(client, fileObj, uploadSession, optionsWithProgress);
119+
const uploadResult: UploadResult = await uploadTask.upload();
120+
```
121+
122+
`UploadResult` contains the `location`(received in the Outlook API response headers) and the `responseBody` (responseBody received after successful upload.) properties.
123+
124+
## OneDriveLargeFileUploadTask.
125+
126+
_You can also use `OneDriveLargeFileUploadTask` which provides easier access to upload to OneDrive API_
127+
128+
Example -
129+
130+
```typescript
131+
const progressCallBack: Progress = {
132+
progress,
133+
completed,
134+
failure,
135+
extraCallBackParams: true,
136+
};
137+
138+
const options: OneDriveLargeFileUploadOptions = {
139+
path: "/Documents",
140+
fileName,
141+
rangeSize: 1024 * 1024,
142+
progressCallBack,
143+
};
144+
const readStream = fs.createReadStream(`./fileName`);
145+
const fileObject = new StreamUpload(readStream, fileName, totalsize);
146+
or
147+
const readFile = fs.readFileSync(`./fileName`);
148+
const fileObject = new FileUpload(readStream, fileName, totalsize);
149+
150+
const uploadTask = await OneDriveLargeFileUploadTask.createTaskWithFileObject(client, fileObject, options);
151+
const uploadResult:UploadResult = await uploadTask.upload();
80152
}
81153
```
82154

155+
> Note: The `OneDriveLargeFileUploadTask.createTaskWithFileObject` also handles the upload session creation.**
156+
83157
## We can just resume the broken upload
84158

85159
_Lets consider some break down happens in the middle of uploading, with the uploadTask object in hand you can resume easily._
@@ -98,38 +172,6 @@ let slicedFile = uploadTask.sliceFile(range);
98172
uploadTask.uploadSlice(slicedFile, range, uploadTask.file.size);
99173
```
100174

101-
## Uploading with custom options
102-
103-
_You can pass in the customized options using LargeFileUploadTask_
104-
105-
```typescript
106-
async function largeFileUpload(client, file) {
107-
const fileName = file.name;
108-
const driveId = "<YOUR_DRIVE_ID>";
109-
const path = "<LOCATION_TO_STORE_FILE>";
110-
try {
111-
const requestUrl = `/drives/${driveId}/root:${path}/${fileName}:/createUploadSession`;
112-
const payload = {
113-
item: {
114-
"@microsoft.graph.conflictBehavior": "fail",
115-
name: fileName,
116-
},
117-
};
118-
const fileObject = {
119-
size: file.size,
120-
content: file,
121-
name: fileName,
122-
};
123-
const uploadSession = await LargeFileUploadTask.createUploadSession(client, requestUrl, payload);
124-
const uploadTask = await new LargeFileUploadTask(client, fileObject, uploadSession);
125-
const response = await uploadTask.upload();
126-
return response;
127-
} catch (err) {
128-
throw err;
129-
}
130-
}
131-
```
132-
133175
## Cancelling a largeFileUpload task
134176

135177
_Cancelling an upload session sends a DELETE request to the upload session URL_

0 commit comments

Comments
 (0)