Fetch API cannot load file:///C:/Users/Name/boxtest.fbx. URL scheme "file" is not supported

After updating from v133 to 134, I get this error.
I assume it is due to this: " FileLoader now uses fetch instead of XMLHttpRequest ."
How can I fix this error? It was working fine before the switch to 134, and there is no migration guide on this topic.

edit: Posted the error on github: r133 -> r134 = error: Fetch API cannot load file:///C:/Users/Name/Downloads/boxtest.fbx. URL scheme "file" is not supported. · Issue #23138 · mrdoob/three.js · GitHub

See How to run things locally — use of a local server is required when developing web applications with three.js; that has been the case for quite a while due to browser security policies.

It was working in r133 though. I use NW.js to run my application locally/natively on windows/mac/linux.

Perhaps NW.js was patching XmlHttpRequest to then. three.js does not officially maintain any support for Node.js, so switching from one browser API (XmlHttpRequest) to another browser API (fetch) would not appear as a migration guide topic.

You could try something like this after installing node-fetch, but I’m not familiar with NW.js enough to say whether it will work; normally you don’t make HTTP requests to paths on disk, and all three.js loaders are designed around HTTP requests.

global.fetch = require('node-fetch');
2 Likes

I tried, but I couldn’t get it to work. I don’t know much about node modules, and it is confusing the information about it. I will ask the nw.js and node-fetch people.

edit: How do I get this working with NW.js? · Discussion #1441 · node-fetch/node-fetch · GitHub

Correct.

It’s not an error, Browsers are just making it harder to access local files.

Up to few months ago Chrome could access local files with this flag –allow-file-access-from-files.
This is not true with Chrome 96.

FF 95 still can access local files in the same working directory or under.
Provided the flag privacy.file_unique_origin is set to false.

If you really like to work with local files without running a server. you should look into drag and drop or input type file interface.

test.html (242 Bytes)

thanks- I’ve tried your code with nw.js and I got the same error. So I’ve reported it to the nw.js dev

I found that using
var url = URL.createObjectURL(e.target.files[0])
can work in some cases to load files, however when loading an MTL file that contains paths to texture images, it won’t load those because it tries to combine the objectURL with the relative paths, creating invalid paths. Another thing is that relative paths can’t work across different harddrives, and the application can be on separate drive from the files that the user wants to load.

The purpose of test.html was to show you the behavior of fetch() API when dealing with file protocol. The error has nothing to do with nw,js nor three.js lib.

Your best option is to set up a virtual host to fetch content of a file programically. window.location.href returns file URL, but that is not going to help you. You need to get the content of texture listed in MTL file not its location.

I’m not familiar with NW.js but i had a similar issue using electron and maybe my workaround gives you an idea (this worked for me).
Since FileLoader works on fetch requests, I detect them and I return my desired modified response.

const { fetch: origFetch } = window;
    window.fetch = async (...args) => {
      let response;
      let args_ = args;
      try { 
           response = await origFetch(...args); 
       } catch (error) { 
           //
       } finally {
        if (args_[0].url.includes('.gltf')) {
          let myLocalFileName= args_[0].url.split('/')[args_[0].url.split('/').length - 1];
          let myLocalFile = await myBackgroundAPI.readExtraResourceFile('extraResources/' + myLocalFileName);
          let myBlob = new Blob([myLocalFile ], { type: 'model/gltf+json' });
          let myModifiedResponse = new Response(myBlob, { status: 200, statusText: 'OK' });
          return myModifiedResponse ;
        }
        if (args_[0].url.includes('.bin')) {
          let myLocalFileName= args_[0].url.split('/')[args_[0].url.split('/').length - 1];
          let myLocalFile = await backgroundAPI.readExtraResourceFile('extraResources/' + myLocalFileName);
          let myBlob = new Blob([myLocalFile ], { type: 'application/octet-stream' });
          let myModifiedResponse = new Response(myBlob , { status: 200, statusText: 'OK' });
          return myModifiedResponse ;
        }
      }
    };

I can’t do that, because I shouldn’t expect users of my application to have to setup their own servers. Also, the .mtl files are suppose to contain paths to other files- that is part of their purpose when working with 3d .obj files. Other 3d formats also contain paths to files. So I need to be able to load via the file url scheme.

Can you explain what your code does? I’m not that familiar with how fetch works- so I’m not sure what your code is doing. Is it modifying the original fetch to make it work differently? So whenever fetch is called, it does the modified version?

In my case I needed to use GLTFLoader (that use FileLoader ”behind the curtains”). It loads a .gltf file that calls (via fetch) for other .bin files (similar to what you wrote).
So in my code when a fetch request is detected, an error is expected. The “try-catch” detects the error and the trick is done in the “finally” block. Since the fetch has failed there in so a fetch response, so a costumed one is created. The needed files in my case are read by another API that uses the normal fs module of node and put in a Blob that becomes the body of the fetch custom response.

Thanks for the explanation. I tried to implement that, and it almost works… In the case of MTLLoader, there is a .preload() to load the textures, however it fails with this error:
GET chrome-extension://jdgkbifmkkkcgglljmlojibojoafmlhi/texture_0.png net::ERR_FILE_NOT_FOUND

So I can’t get the textures to load that the MTL file references. It manages to load the .mtl file, but then it can’t continue because it uses some other way to load the textures.

If I look in the MTLLoader, it seems it goes wrong here:
const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );

I’m not familiar with MTLLoader and neither with .mtl files. Can you try to feagure out the sequence of the requested files?
In my case:

  1. .gltf file is requested
  2. GLTFLoader parse the files and finds that another .bin file is needed
  3. the .bin needed file is requested (in the local system it must have the same path of the .gltf file)

It’s the same sequence:
.mtl file is requested and loaded, and upon loaded the MTLLoader retrieves the file paths that are stored inside the .mtl file and attempts to load the textures into the new materials. The file paths are relative( I think ), ex: “texture_0.png”… however it combines it with “chrome-extension://jdgkbifmkkkcgglljmlojibojoafmlhi/” which is an invalid path.

It combines the two like this in the mtlloader
const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
You can see the scope.baseURL is the “chrome-extension://jdgkbifmkkkcgglljmlojibojoafmlhi/”

When the texture_0.png file is needed, another fetch request is performed I think. So you should have to use the same trick as before.

I already modified the fetch though.

const { fetch: origFetch } = window;
    window.fetch = async (...args) => {
      let response;
      let args_ = args;
      try { 
           response = await origFetch(...args); 
       } catch (error) { 
           //
       } finally {
		
		const path = decodeURIComponent(args_[0].url.split('///')[1]);	
		const fs = require('fs')

		try {
		  const data = fs.readFileSync(path);
		  
		  let myBlob = new Blob([data ], { type: '' });
          let myModifiedResponse = new Response(myBlob, { status: 200, statusText: 'OK' });
          return myModifiedResponse ;
		  
		} catch (err) {
		  console.error(err);
		}

      }
    };

The issue is that you end up with different paths trying to combine, because it doesn’t transfer the new path.

You could try to

console.log(path)

I think that you have to manage more in details each fetch.
When you load the .mtl file, you specify ‘totally’ the path/url while the other requested files the path/url is generated by the threejs loaders function and not by you.
If you go back to my case, I detect the fetch of .bin files. These fetch are not directly made by me but are generated by the loader of threejs.