Maker of open-source software and hardware.

Taking a deep dive into MTP: Part 5

This is the final instalment of my second series of blog posts on MTP. I now have my code working in both the browser and Node.js, where it connects to an Android device, reads the latest file and saves it locally.

First off, I had to write some code to figure out whether we're in the browser, in Node.js or in Electron:

let isBrowser = null;

if (typeof navigator !== 'undefined') {
  const userAgent = navigator.userAgent.toLowerCase();
  isBrowser = userAgent.indexOf(' electron/') === -1 && typeof window !== 'undefined';
} else {
  // Node.js process
  isBrowser = false;
}

I'm sure there are modules out there that do this better, but it works for now. Now we have to load some modules for Node.js when we're not in the browser:

let usb, fs = null;

if (!isBrowser) {
  // For Node.js and Electron
  usb = require('webusb').usb;
  EventTarget = require('events');
  fs = require('fs');
} else {
  usb = navigator.usb; // Yay, we're using WebUSB!
}

We also need to be able to save the file to disk in both the browser and Node.js:

const array = new Uint8Array(data.payload);

if (isBrowser) {
  const file = new Blob([array]);
  const a = document.createElement('a'),
  url = URL.createObjectURL(file);
  a.href = url;
  a.download = this.filename;
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 0);
} else {
  fs.writeFileSync(this.filename, array);
}

We also need to deal with event listeners differently in the browser and Node.js:

const initMTP = () => {
  const mtp = new Mtp(0x0e8d, 0x201d);

  if (isBrowser) {
    mtp.addEventListener('error', err => console.log('Error', err));

    mtp.addEventListener('ready', async () => {
      mtp.addEventListener('data', (e) => mtp.dataHandler(e.detail));
      await mtp.openSession();
    } );
  } else {
    mtp.on('error', err => console.log('Error', err));
    mtp.on('ready', async () => {
      mtp.on('data', (data) => mtp.dataHandler(data));
      await mtp.openSession();
    });
  }
};

And finally, WebUSB requires a user interaction before it will show the permission prompt, so we need:

if (isBrowser) {
  document.addEventListener('DOMContentLoaded', event => {
    let button = document.getElementById('connect');

    button.addEventListener('click', async() => {
      initMTP();
    });
 });
} else {
  initMTP();
}

So, that's it! Over on GitHub I have the full implementation. If you run it in Node.js, it will save the newest file on your Android device to disk. If you run it in the browser, it will do the same after you click the Connect button:

<html>
  <head>
    <title>MTP over WebUSB</title>
    <script src="mtp.js"></script>
  </head>
  <body>
    <button id="connect">Connect</button>
  </body>
</html>

I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.

#100DaysToOffload #day54 #mtp