jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
4.01k stars 417 forks source link

catch thrown error after failed serviceWorker registration #216

Open gDev95 opened 3 years ago

gDev95 commented 3 years ago

Hi,

I am currently working on problem where a service worker cannot be registered in firefox because of user settings (the problem can be found on stackoverflow)

I am rethrowing the error but its not caught anywhere.

I extracted the mitmHTML script into its own .tsx file but it should be familiar ;)

mitmEntry.tsx

/* eslint-disable no-console */
import registerServiceWorker from "service-worker-loader!./serviceWorker";

// This is copied from here https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html
// eslint-disable
/*
    mitm.html is the lite "man in the middle"

    This is only meant to signal the opener's messageChannel to
    the service worker - when that is done this mitm can be closed
    but it's better to keep it alive since this also stops the sw
    from restarting

    The service worker is capable of intercepting all request and fork their
    own "fake" response - wish we are going to craft
    when the worker then receives a stream then the worker will tell the opener
    to open up a link that will start the download
*/

// This will prevent the sw from restarting
let keepAlive = () => {
  keepAlive = () => {};
  let ping = location.href.substr(0, location.href.lastIndexOf("/")) + "/ping";
  const interval = setInterval(() => {
    if (sw) {
      sw.postMessage("ping");
    } else {
      // @ts-ignore
      fetch(ping).then((res) => res.text(!res.ok && clearInterval(interval)));
    }
  }, 10000);
};

// message event is the first thing we need to setup a listner for
// don't want the opener to do a random timeout - instead they can listen for
// the ready event
// but since we need to wait for the Service Worker registration, we store the
// message for later
let messages: Array<any> = [];
window.onmessage = (evt) => messages.push(evt);

let sw: ServiceWorker | null = null;
let scope = "";
let fn;

function registerWorker() {
  return registerServiceWorker()
    .then((swReg) => {
      const swRegTmp = swReg.installing || swReg.waiting;

      scope = swReg.scope;

      return (
        (sw = swReg.active) ||
        new Promise((resolve) => {
          swRegTmp?.addEventListener(
            "statechange",
            (fn = () => {
              if (swRegTmp.state === "activated") {
                swRegTmp.removeEventListener("statechange", fn);
                sw = swReg.active;
                resolve();
              }
            })
          );
        })
      );
    })
    .catch((error) => {
      throw error;
    });
}

  [...] // trying to reduce the code here for you 

  const transferable = data.readableStream ? [ports[0], data.readableStream] : [ports[0]];

  if (!(data.readableStream || data.transferringReadable)) {
    keepAlive();
  }

  return sw?.postMessage(data, transferable);
}

if (window.opener) {
  // The opener can't listen to onload event, so we need to help em out!
  // (telling them that we are ready to accept postMessage's)
  window.opener.postMessage("StreamSaver::loadedPopup", "*");
}

if (navigator.serviceWorker) {
  registerWorker()
    .then(() => {
      window.onmessage = onMessage;
      messages.forEach(window.onmessage);
    })
    .catch((error) => {
      console.error("Registring worker for StreamSaver API has failed");
      throw error; // => I rethrow and hope to catch this error
    });
} else {
  // FF can ping sw with fetch from a secure hidden iframe
  // shouldn't really be possible?
  keepAlive();
}

Now I have two questions:

Is this running in a different thread than the main thread ?

When is streamsaver grabbing the mitmHTML file (which exectute this script)?

Best regards,

Paul

jimmywarting commented 3 years ago

Is this running in a different thread than the main thread ?

the mitm only forwards a message port (in some newer cases a readable stream) from the main thread to the service worker - that's it. once that is done it could almost basically be closed - but it sticks around to keep the service worker alive by pinging the service worker.

But yea, you are basically saving stuff from the main thread (top window) where you host the main streamsaver lib.

When is streamsaver grabbing the mitmHTML file (which exectute this script)?

StreamSaver opens up a iframe to mitm when it's time to open up a download stream (meaning it's lazy loaded when needed)


If you would like to

... then i recommend you to have a look at my new File System Adapter i recommend you to at least take a look at it. it can work without a service worker but it's recommended to install one for larger files to achieve similar functionality as StreamSaver

gDev95 commented 3 years ago

When I throw an error after the promise is rejected during the registration of the worker its never caught. I could trace back, based on your information regarding the lazy loading, that once we first write into the stream, the worker will try to register.

I have multiple layers of error handling that should catch the error and log/treat the error respectively but its nowhere caught.

The console however lets me know that the is an uncaught exception.

I could assume since the streamsaver module actually initiates the connection to the mitm it is not caught in streamsaver code and therefor falls through and isn't noticed by my error handling layers?

Or could be since the script is running inside an iframe the error is not bubbling to its caller?

Cannot find another explanation, do you have an idea?