Files
fps_project_1/addons/godot-firebase/database/reference.gd

177 lines
5.8 KiB
GDScript

## @meta-authors BackAt50Ft
## @meta-version 2.4
## A reference to a location in the Realtime Database.
## Documentation TODO.
@tool
class_name FirebaseDatabaseReference
extends Node
signal new_data_update(data)
signal patch_data_update(data)
signal delete_data_update(data)
signal once_successful(dataSnapshot)
signal once_failed()
signal push_successful()
signal push_failed()
const ORDER_BY : String = "orderBy"
const LIMIT_TO_FIRST : String = "limitToFirst"
const LIMIT_TO_LAST : String = "limitToLast"
const START_AT : String = "startAt"
const END_AT : String = "endAt"
const EQUAL_TO : String = "equalTo"
@onready var _pusher := $Pusher
@onready var _listener := $Listener
@onready var _store := $DataStore
var _auth : Dictionary
var _config : Dictionary
var _filter_query : Dictionary
var _db_path : String
var _cached_filter : String
var _can_connect_to_host : bool = false
const _put_tag : String = "put"
const _patch_tag : String = "patch"
const _delete_tag : String = "delete"
const _separator : String = "/"
const _json_list_tag : String = ".json"
const _query_tag : String = "?"
const _auth_tag : String = "auth="
const _accept_header : String = "accept: text/event-stream"
const _auth_variable_begin : String = "["
const _auth_variable_end : String = "]"
const _filter_tag : String = "&"
const _escaped_quote : String = '"'
const _equal_tag : String = "="
const _key_filter_tag : String = "$key"
var _headers : PackedStringArray = []
func _ready() -> void:
#region Set Listener info
$Listener.new_sse_event.connect(on_new_sse_event)
var base_url = _get_list_url(false).trim_suffix(_separator)
var extended_url = _separator + _db_path + _get_remaining_path(false)
var port = -1
if Firebase.emulating:
port = int(_config.emulators.ports.realtimeDatabase)
$Listener.connect_to_host(base_url, extended_url, port)
#endregion Set Listener info
#region Set Pusher info
$Pusher.queue_request_completed.connect(on_push_request_complete)
#endregion Set Pusher info
func set_db_path(path : String, filter_query_dict : Dictionary) -> void:
_db_path = path
_filter_query = filter_query_dict
func set_auth_and_config(auth_ref : Dictionary, config_ref : Dictionary) -> void:
_auth = auth_ref
_config = config_ref
func on_new_sse_event(headers : Dictionary, event : String, data : Dictionary) -> void:
if data:
var command = event
if command and command != "keep-alive":
_route_data(command, data.path, data.data)
if command == _put_tag:
if data.path == _separator and data.data and data.data.keys().size() > 0:
for key in data.data.keys():
new_data_update.emit(FirebaseResource.new(_separator + key, data.data[key]))
elif data.path != _separator:
new_data_update.emit(FirebaseResource.new(data.path, data.data))
elif command == _patch_tag:
patch_data_update.emit(FirebaseResource.new(data.path, data.data))
elif command == _delete_tag:
delete_data_update.emit(FirebaseResource.new(data.path, data.data))
func update(path : String, data : Dictionary) -> void:
path = path.strip_edges(true, true)
if path == _separator:
path = ""
var to_update = JSON.stringify(data)
var resolved_path = (_get_list_url() + _db_path + "/" + path + _get_remaining_path())
_pusher.request(resolved_path, _headers, HTTPClient.METHOD_PATCH, to_update)
func push(data : Dictionary) -> void:
var to_push = JSON.stringify(data)
_pusher.request(_get_list_url() + _db_path + _get_remaining_path(), _headers, HTTPClient.METHOD_POST, to_push)
func delete(reference : String) -> void:
_pusher.request(_get_list_url() + _db_path + _separator + reference + _get_remaining_path(), _headers, HTTPClient.METHOD_DELETE, "")
#
# Returns a deep copy of the current local copy of the data stored at this reference in the Firebase
# Realtime Database.
#
func get_data() -> Dictionary:
if _store == null:
return { }
return _store.get_data()
func _get_remaining_path(is_push : bool = true) -> String:
var remaining_path = ""
if _filter_query_empty() or is_push:
remaining_path = _json_list_tag + _query_tag + _auth_tag + Firebase.Auth.auth.idtoken
else:
remaining_path = _json_list_tag + _query_tag + _get_filter() + _filter_tag + _auth_tag + Firebase.Auth.auth.idtoken
if Firebase.emulating:
remaining_path += "&ns="+_config.projectId+"-default-rtdb"
return remaining_path
func _get_list_url(with_port:bool = true) -> String:
var url = Firebase.Database._base_url.trim_suffix(_separator)
if with_port and Firebase.emulating:
url += ":" + _config.emulators.ports.realtimeDatabase
return url + _separator
func _get_filter():
if _filter_query_empty():
return ""
# At the moment, this means you can't dynamically change your filter; I think it's okay to specify that in the rules.
if _cached_filter != "":
_cached_filter = ""
if _filter_query.has(ORDER_BY):
_cached_filter += ORDER_BY + _equal_tag + _escaped_quote + _filter_query[ORDER_BY] + _escaped_quote
_filter_query.erase(ORDER_BY)
else:
_cached_filter += ORDER_BY + _equal_tag + _escaped_quote + _key_filter_tag + _escaped_quote # Presumptuous, but to get it to work at all...
for key in _filter_query.keys():
_cached_filter += _filter_tag + key + _equal_tag + _filter_query[key]
return _cached_filter
func _filter_query_empty() -> bool:
return _filter_query == null or _filter_query.is_empty()
#
# Appropriately updates the current local copy of the data stored at this reference in the Firebase
# Realtime Database.
#
func _route_data(command : String, path : String, data) -> void:
if command == _put_tag:
_store.put(path, data)
elif command == _patch_tag:
_store.patch(path, data)
elif command == _delete_tag:
_store.delete(path, data)
func on_push_request_complete(result : int, response_code : int, headers : PackedStringArray, body : PackedByteArray) -> void:
if response_code == HTTPClient.RESPONSE_OK:
push_successful.emit()
else:
push_failed.emit()