## @meta-authors Nicolò 'fenix' Santilio, ## @meta-version 2.5 ## ## (source: [url=https://firebase.google.com/docs/functions]Functions[/url]) ## ## @tutorial https://github.com/GodotNuts/GodotFirebase/wiki/Functions @tool class_name FirebaseFunctions extends Node ## Emitted when a [code]query()[/code] request is successfully completed. [code]error()[/code] signal will be emitted otherwise. ## @arg-types Array ## Emitted when a [code]list()[/code] or [code]query()[/code] request is [b]not[/b] successfully completed. signal task_error(code,status,message) # TODO: Implement cache size limit const CACHE_SIZE_UNLIMITED = -1 const _CACHE_EXTENSION : String = ".fscache" const _CACHE_RECORD_FILE : String = "RmlyZXN0b3JlIGNhY2hlLXJlY29yZHMu.fscache" const _AUTHORIZATION_HEADER : String = "Authorization: Bearer " const _MAX_POOLED_REQUEST_AGE = 30 ## The code indicating the request Firestore is processing. ## See @[enum FirebaseFirestore.Requests] to get a full list of codes identifiers. ## @enum Requests var request : int = -1 ## Whether cache files can be used and generated. ## @default true var persistence_enabled : bool = false ## Whether an internet connection can be used. ## @default true var networking: bool = true : set = set_networking ## A Dictionary containing all authentication fields for the current logged user. ## @type Dictionary var auth : Dictionary var _config : Dictionary = {} var _cache_loc: String var _encrypt_key: String = "" if Utilities.is_web() else OS.get_unique_id() var _base_url : String = "" var _http_request_pool : Array = [] var _offline: bool = false : set = _set_offline func _ready() -> void: set_process(false) func _process(delta : float) -> void: for i in range(_http_request_pool.size() - 1, -1, -1): var request = _http_request_pool[i] if not request.get_meta("requesting"): var lifetime: float = request.get_meta("lifetime") + delta if lifetime > _MAX_POOLED_REQUEST_AGE: request.queue_free() _http_request_pool.remove_at(i) return # Prevent setting a value on request after it's already been queue_freed request.set_meta("lifetime", lifetime) ## @args ## @return FunctionTask func execute(function: String, method: int, params: Dictionary = {}, body: Dictionary = {}) -> FunctionTask: set_process(true) var function_task : FunctionTask = FunctionTask.new() function_task.task_error.connect(_on_task_error) function_task.task_finished.connect(_on_task_finished) function_task.function_executed.connect(_on_function_executed) function_task._method = method var url : String = _base_url + ("/" if not _base_url.ends_with("/") else "") + function function_task._url = url if not params.is_empty(): url += "?" for key in params.keys(): url += key + "=" + params[key] + "&" if not body.is_empty(): function_task._fields = JSON.stringify(body) _pooled_request(function_task) return function_task func set_networking(value: bool) -> void: if value: enable_networking() else: disable_networking() func enable_networking() -> void: if networking: return networking = true _base_url = _base_url.replace("storeoffline", "functions") func disable_networking() -> void: if not networking: return networking = false # Pointing to an invalid url should do the trick. _base_url = _base_url.replace("functions", "storeoffline") func _set_offline(value: bool) -> void: if value == _offline: return _offline = value if not persistence_enabled: return return func _set_config(config_json : Dictionary) -> void: _config = config_json _cache_loc = _config["cacheLocation"] if _encrypt_key == "": _encrypt_key = _config.apiKey _check_emulating() func _check_emulating() -> void : ## Check emulating if not Firebase.emulating: _base_url = "https://{zone}-{projectId}.cloudfunctions.net/".format({ zone = _config.functionsGeoZone, projectId = _config.projectId }) else: var port : String = _config.emulators.ports.functions if port == "": Firebase._printerr("You are in 'emulated' mode, but the port for Cloud Functions has not been configured.") else: _base_url = "http://localhost:{port}/{projectId}/{zone}/".format({ port = port, zone = _config.functionsGeoZone, projectId = _config.projectId }) func _pooled_request(task : FunctionTask) -> void: if _offline: task._on_request_completed(HTTPRequest.RESULT_CANT_CONNECT, 404, PackedStringArray(), PackedByteArray()) return if auth == null or auth.is_empty(): Firebase._print("Unauthenticated request issued...") Firebase.Auth.login_anonymous() var result : Array = await Firebase.Auth.auth_request if result[0] != 1: _check_auth_error(result[0], result[1]) Firebase._print("Client connected as Anonymous") task._headers = ["Content-Type: application/json", _AUTHORIZATION_HEADER + auth.idtoken] var http_request : HTTPRequest for request in _http_request_pool: if not request.get_meta("requesting"): http_request = request break if not http_request: http_request = HTTPRequest.new() Utilities.fix_http_request(http_request) http_request.accept_gzip = false _http_request_pool.append(http_request) add_child(http_request) http_request.request_completed.connect(_on_pooled_request_completed.bind(http_request)) http_request.set_meta("requesting", true) http_request.set_meta("lifetime", 0.0) http_request.set_meta("task", task) http_request.request(task._url, task._headers, task._method, task._fields) # ------------- func _on_task_finished(data : Dictionary) : pass func _on_function_executed(result : int, data : Dictionary) : pass func _on_task_error(code : int, status : int, message : String): task_error.emit(code, status, message) Firebase._printerr(message) func _on_FirebaseAuth_login_succeeded(auth_result : Dictionary) -> void: auth = auth_result func _on_FirebaseAuth_token_refresh_succeeded(auth_result : Dictionary) -> void: auth = auth_result func _on_pooled_request_completed(result : int, response_code : int, headers : PackedStringArray, body : PackedByteArray, request : HTTPRequest) -> void: request.get_meta("task")._on_request_completed(result, response_code, headers, body) request.set_meta("requesting", false) func _on_connect_check_request_completed(result : int, _response_code, _headers, _body) -> void: _set_offline(result != HTTPRequest.RESULT_SUCCESS) func _on_FirebaseAuth_logout() -> void: auth = {} func _check_auth_error(code : int, message : String) -> void: var err : String match code: 400: err = "Please, enable Anonymous Sign-in method or Authenticate the Client before issuing a request (best option)" Firebase._printerr(err)