Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic import of Javascript module from stream

Goal: To support dynamic loading of Javascript modules contingent on some security or defined user role requirement such that even if the name of the module is identified in dev tools, it cannot be successfully imported via the console.

enter image description here

enter image description here

enter image description here

A JavaScript module can be easily uploaded to a cloud storage service like Firebase (#AskFirebase) and the code can be conditionally retrieved using a Firebase Cloud Function firebase.functions().httpsCallable("ghost"); based on the presence of a custom claim or similar test.

export const ghost = functions.https.onCall(async (data, context) => {
  if (! context.auth.token.restrictedAccess === true) {
    throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
  }

  const storage = new Storage();
  const bucketName = 'bucket-name.appspot.com';
  const srcFilename = 'RestrictedChunk.chunk.js';

  // Downloads the file
  const response = await storage
    .bucket(bucketName)
    .file(srcFilename).download();
  const code = String.fromCharCode.apply(String, response[0])

  return {source: code};

})

In the end, what I want to do...

...is take a webpack'ed React component, put it in the cloud, conditionally download it to the client after a server-side security check, and import() it into the user's client environment and render it.

Storing the Javascript in the cloud and conditionally downloading to the client are easy. Once I have the webpack'ed code in the client, I can use Function(downloadedRestrictedComponent) to add it to the user's environment much as one would use import('./RestrictedComponent') but what I can't figure out is how to get the default export from the component so I can actually render the thing.

import(pathToComponent) returns the loaded module, and as far as I know there is no option to pass import() a string or a stream, just a path to the module. And Function(downloadedComponent) will add the downloaded code into the client environment but I don't know how to access the module's export(s) to render the dynamically loaded React components.

Is there any way to dynamically import a Javascript module from a downloaded stream?

Edit to add: Thanks for the reply. Not familiar with the nuances of Blobs and URL.createObjectURL. Any idea why this would be not found?

const ghost = firebase.functions().httpsCallable("ghost");

const LoadableRestricted = Loadable({
  //  loader: () => import(/* webpackChunkName: "Restricted" */ "./Restricted"),
  loader: async () => {
    const ghostContents = await ghost();
    console.log(ghostContents);
    const myBlob = new Blob([ghostContents.data.source], {
      type: "application/javascript"
    });
    console.log(myBlob);
    const myURL = URL.createObjectURL(myBlob);
    console.log(myURL);
    return import(myURL);
  },
  render(loaded, props) {
    console.log(loaded);
    let Component = loaded.Restricted;
    return <Component {...props} />;
  },
  loading: Loading,
  delay: 2000
});

enter image description here

like image 457
shaz Avatar asked Oct 19 '18 15:10

shaz


1 Answers

Read the contents of the module file/stream into a BLOB. The use URL.createObjectURL() to create your dynamic URL to the BLOB. Now use import as you suggested above:

import(myBlobURL).then(module=>{/*doSomethingWithModule*/});
like image 139
Randy Casburn Avatar answered Sep 21 '22 01:09

Randy Casburn