Skip to content

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 package
  • packageToken is the authorized JWT token for the package, also obtained at time of package creation
  • apiURL is the base URL of MASV API, which should be https://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 reports completed bytes so far, and total byte size of all files. The speed object determined the calculated upload speed in various forms (all in bits per second): instant for the instant speed since the last onProgress call, average speed is the rolling average of upload speed since the beginning of the upload and moving 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 completed
  • onRetry 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 finalized
  • onError 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>