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://massive.app/lib/masv-uploader-beta.min.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>