MASV JavaScript Web Uploader
Note
MASV provides this version of the Javascript Uploader for legacy applications only. For new web applications or when updating legacy applications, we recommend that you use the Web Uploader instead.
Getting Started
Include the uploader library in your page:
<script src="https://dl.massive.io/masv-uploader.js"></script>
Create a new instance of it and pass the parameters :
const uploader = new MASV.Uploader(
packageID,
packageToken,
apiURL,
);
Where:
pakageID
is the ID of the package for which the files will be uploaded. This can be obtained by creating a portal package or a team packagepackageToken
is the authorized JWT token for the package, also obtained at time of package creationapiURL
is the base URL of MASV API, which should behttps://api.massive.app
for production
Usage
Initialization
After you instantiate the uploader object, you need to create a listener to handle various upload event callbacks.
uploader.setListener(listener)
Here's the listener's interface definition (in TypeScript, for clarity):
interface MasvListener {
onProgress: (completed: number, total: number, speed: {
instant: number;
average: number;
moving: number;
}) => void;
onFileComplete: (file: MasvFile) => void;
onRetry: (reason: Error) => void;
onComplete: () => void;
onError: (err: Error) => void;
}
Where:
onProgress
is called when upload progress occurs (bytes getting uploaded). It reportscompleted
bytes so far, andtotal
byte size of all files. Thespeed
object determined the calculated upload speed in various forms (all in bits per second):instant
for the instant speed since the lastonProgress
call,average
speed is the rolling average of upload speed since the beginning of the upload andmoving
speed is the moving average of upload speed in the last 30 seconds.onFileComplete
is called whenever one of the supplied files finishes uploading, it indicates which file object was completedonRetry
is called when the uploader retries any request (for example a file chunk upload) due to an error that is deemed retriable by the uploader (for example, a network error or a 5xx server error)onComplete
is called when the upload is completed and the package have been finalizedonError
is called when an unrecoverable error has occurred and the upload halted (for example, a 401 server error is considered unrecoverable)
Please note that the progress is not necessarily monotonic. As the uploader processes chunks, it reports the progress inclusive of chunks that are in flight. In the event of a retry due to a retriable error, it resets the progress to the previous value prior to uploading the chunk since chunks have to be uploaded entirely. It's up to you to handle this behaviour in the UI.
Uploading files
After you have setup the callback listener it's just a matter of passing the list of files you want to upload:
uploader.upload(files:MasvFile[])
Attention
Please note that the uploader ignores the following files/directories because they tend to change while upload is running which would cause the upload to fail:
- `desktop.ini`
- `.DS_Store`
- `.fcpcache`
Each MasvFile object must conform to the following interface (in TypeScript, for clarity):
interface MasvFile {
id: string;
file: File; // JS File object, which can be obtained from a file input for example
path: string;
}
Cancelling
Warning
A cancelled upload cannot be resumed.
An active upload can be cancelled by calling
uploader.terminate()
Code Example
The following example demonstrates a portal upload. You can view a fully working page hosted here:
Attention
Please note that the uploads in this example go to https://js-uploader-test.portal.massive.app/ portal, which is intended for internal testing by MASV team.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://dl.massive.io/masv-uploader.js"></script>
<title>Simple MASV Uploader</title>
<style>
code {
background-color: #f1f1f1;
font-size: 105%;
}
div {
margin: 10px;
}
</style>
</head>
<body>
<h1>MASV Uploader Test Page</h1>
<input id="fileupload" type="file" multiple />
<button type="button" onclick="startUpload()">Upload</button>
<div>
<code readonly id="status"></code>
</div>
<script>
async function fetchPortalID(subdomain){
// API Call
const resp = await fetch(
`https://api.massive.app/v1/subdomains/portals/${subdomain}`
);
// Proper error Handling should be done here if needed
// e.g. if(!response.ok) { alert('could not load portal info')}
// Return portal ID
const data = await resp.json();
return data.id;
}
//Package Creation Step
async function createPackage(portalID, senderEmail, packageName = "Web uploader test", packageDescription = "") {
// This example is using a portal Upload
const headers = { "Content-Type": "application/json" };
// If its a portal upload and it's password protected send the password in the header
// headers["X-Access-Code"] = encodeURIComponent('topsecret');
// Sender email is required; package name and message are optional and can be empty strings
const body = {
sender: senderEmail,
name: packageName,
description: packageDescription
};
// API Call
const resp = await fetch(
`https://api.massive.app/v1/portals/${portalID}/packages`,
{
method: "POST",
headers: headers,
body: JSON.stringify(body)
}
);
// Proper error Handling should be done here if needed
// eg. if(!response.ok) { alert('Insufficient permission, for example in a password protected portal')}
// Handle Data
const data = await resp.json();
return {
packageID: data.id,
packageToken: data.access_token
}
}
async function startUpload(e) {
// This will upload a MASV Package to a portal for simplicity, since they don't require user login
// the same process can be used to send to an email recipient (via teams API)
// as the uploader only needs a package ID and token, regardless of the type of package
// Load portal ID by subdomain; in this case https://js-uploader-test.portal.massive.app/
const portalID = await fetchPortalID("js-uploader-test");
// Create the package and obtain ID and token
// You can change the sender email to any value you want
const {packageID, packageToken} = await createPackage(portalID, "[email protected]");
// Example of how to handle if the user tries to close the window while uploader is still working
window.onbeforeunload = function (e) {
e.preventDefault();
e.returnValue = "Your upload will be interrupted";
};
// Grab the fileList array from the input and create MASV File object list
const fileList = document.getElementById('fileupload').files;
const filesArray = [];
for (var i = 0; i < fileList.length; i++) {
filesArray.push({
file: fileList[i],
path: "",
id: i
});
}
// Indicate that the upload has started
const statusEl = document.getElementById('status');
statusEl.innerHTML = 'Started';
// Create the uploader object
const uploader = new MASV.Uploader(
packageID,
packageToken,
"https://api.massive.app" // Production API
);
// Set up the callback listener as per documentation
let listener = {
onComplete: async () => {
// This callback means the upload is complete and the package has been finalized
// We'll clear the window close prompt
window.onbeforeunload = null;
statusEl.innerHTML = 'Completed';
},
onError: e => {
// This is called when an unrecoverable uploader error occurs
// At this point, the upload is halted, no further data will be sent and the upload has to be retried
console.log("Unrecoverable upload error", e);
window.onbeforeunload = null;
},
onFileComplete: f => {
// This callback means a single file has finished uploading
// This is useful if your UI displays some sort of checklist for each file being uploaded
console.log("File uploadee completed:", f.file.name);
},
onProgress: (transferred, total, speedStat) => {
// This callback is called as upload progresses
// You can think about it like a progression channel, so you can use its responses
// to give visual feedback for what's happening to the user, like displaying progress
// Total and transferred are in BYTES
console.log('Transferred', transferred, 'bytes of', total);
// The speedStat object has 3 properties, all in bits per second:
// - instant: which is the instant speed since the last onProgress call
// - moving: which is a moving average of speed in the past 30 seconds. Useful for long-running uploads since it takes a long time to ramp up but tolerates fluctuations
// - average: which is the rolling average of speed since the beginning of the upload
// Here's an example of how to calcule an ETA based on average speed (notice the conversion from bytes to bits)
console.log('ETA: ', ((total - transferred) * 8) / 1024 / 1024 / speedStat.average);
// Calculate and set progress
const progress = Math.floor(transferred/total * 100)
statusEl.innerHTML = `Upload progress: ${progress}%`
},
onRetry: e => {
// This callback is called every time the uploader retries a request (e.g. a file chunk) due to an underlying
// error that is deemed retriable (for example, network error)
console.log("retrying because:", e);
}
}
// Set the listener
uploader.setListener(listener);
// Start the actual upload
uploader.upload(...filesArray);
}
</script>
</body>
</html>