Issue with calling a GDscript function from Javascipt

Godot Version

Godot 4.7.2 stable

Question

I've been trying to make a system for uploading files from the users filesystem to the game for an import/export save function. The issue is that this is on the web, which makes it extraordinarily difficult. Using the FileAccessWeb plugin I was able to get to the point of picking the file, and from the Javascript side it seems to know the contents of it, however I've been having a lot of issues getting that information back to the GDscript side. Below I have attached the code tracing just the one callback (I have tried with this code and it didn't work.)

class_name FileAccessWeb
extends RefCounted

signal loaded(file_name: String, file_type: String, base64_data: String)

var _file_uploading: JavaScriptObject

var _on_file_loaded_callback: JavaScriptObject

func _init() -> void:
	if _is_not_web():
		_notify_error()
		return
	
	JavaScriptBridge.eval(js_source_code, true)
	_file_uploading = JavaScriptBridge.get_interface("godotFileAccessWeb")
	
	_on_file_loaded_callback = JavaScriptBridge.create_callback(_on_file_loaded)
	
	_file_uploading.setLoadedCallback(_on_file_loaded_callback)

func _on_file_loaded(args: Array) -> void:
	print("Loaded, ", args)                ##Doesn't print.
	loaded.emit(args)

const js_source_code = """
function godotFileAccessWebStart() {
	var loadedCallback;

	var input = document.createElement("input");
	input.setAttribute("type", "file");

	var interface = {
		setLoadedCallback: (loaded) => loadedCallback = loaded,

		setAcceptFiles: (files) => input.setAttribute("accept", files),
		open: () => input.click()
	}
	
	input.onchange = (event) => {
		var file = event.target.files[0];
		
		var reader = new FileReader();
		reader.readAsDataURL(file);

		reader.onload = (readerEvent) => {
			if (readerEvent.target.readyState === FileReader.DONE) {
				console.log("Loaded! (Should be calling back.)");
				loadedCallback(file.name, readerEvent.target.result); 
				console.log("Loaded! (Should have called back.)");  ##Logs both of these in developer console
				console.log(loadedCallback);    ##Log seems very confusing, unsure what it mean.
			}
		}
	}

	return interface;
}

var godotFileAccessWeb = godotFileAccessWebStart();
"""

For reference, this is what console.log(loadedCallback) prints.

To recap,

  • Javascript has the data required (I tried to console.log the file name and data a while ago)
  • The callback to the Godot function does not seem to be doing anything

I feel like I have tried everything at this point and I really am unsure what else to do to fix this. Any help would be appreciated. Thanks.

Forgot to put it in the original, but the FileAccessWeb source code can be found here.

How are you using it? RefCounted will be deleted when it goes out of scope so if you are doing something like:

func load() -> void:
    var file_access_web = FileAccessWeb.new()
    file_access_web.loaded.connect(_on_file_loaded)
    file_access_web.open()

It will probably fail because the file_access_web object gets deleted when exiting the function and won’t emit any signal.

That is actually exactly what I am doing pretty much.

func _import():
	var file_access_web := FileAccessWeb.new()
	file_access_web.load_started.connect(started)
	file_access_web.progress.connect(progress)
	file_access_web.loaded.connect(post)
	file_access_web.error.connect(fail)
	file_access_web.open(".txt")

How would you recommend I fix this? I figured it wouldn’t go out of scope because it still exists as a variable.

You have to keep a reference to it. Something like:

var file_access_web: FileAccessWeb


func _init():
	file_access_web := FileAccessWeb.new()
	file_access_web.load_started.connect(started)
	file_access_web.progress.connect(progress)
	file_access_web.loaded.connect(post)
	file_access_web.error.connect(fail)


func _import():
	file_access_web.open(".txt")

Thanks! That helped. All I needed to do all along was keep the variable outside of the function.