room joining mechanism, a lot of reworks
This commit is contained in:
parent
c50857661f
commit
8bca00b9bb
@ -5,7 +5,7 @@ var _connection
|
|||||||
onready var n_Connection := get_node("Connections")
|
onready var n_Connection := get_node("Connections")
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
_connection = n_Connection.establish_new_connection("poto.cafe", "veclavtalica", "-")
|
_connection = n_Connection.establish_new_connection("poto.cafe", "veclavtalica", "dynamite-onlooker3-snowman")
|
||||||
if _connection == null:
|
if _connection == null:
|
||||||
push_error("Connection failed")
|
push_error("Connection failed")
|
||||||
return
|
return
|
||||||
@ -18,7 +18,7 @@ func _ready():
|
|||||||
push_error("Service builder errored out: %s" % [service_request.value])
|
push_error("Service builder errored out: %s" % [service_request.value])
|
||||||
return
|
return
|
||||||
|
|
||||||
for service in service_request.value.value:
|
for service in service_request.value:
|
||||||
if service.muc == null: continue
|
if service.muc == null: continue
|
||||||
|
|
||||||
var rooms_request = service.muc.request_rooms()
|
var rooms_request = service.muc.request_rooms()
|
||||||
@ -29,5 +29,5 @@ func _ready():
|
|||||||
push_error("Room request errored out: %s" % [rooms_request.value])
|
push_error("Room request errored out: %s" % [rooms_request.value])
|
||||||
return
|
return
|
||||||
|
|
||||||
for room in rooms_request.value.value:
|
for room in rooms_request.value:
|
||||||
print(room.as_string())
|
var me = service.muc.join_room(room, "tochie-facade")
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name Connections
|
class_name Connections
|
||||||
|
|
||||||
|
# todo: Settle on whether connection should send Sums.Result or receiver should check stanza errors by itself.
|
||||||
|
|
||||||
class Connection extends Reference:
|
class Connection extends Reference:
|
||||||
var stream: StreamPeer
|
var stream: StreamPeer
|
||||||
var identity: String
|
var identity: String
|
||||||
@ -10,7 +12,7 @@ class Connection extends Reference:
|
|||||||
|
|
||||||
var _id_counter: int = 0
|
var _id_counter: int = 0
|
||||||
var _pending_iqs: Dictionary # of id to GDScriptFunctionState
|
var _pending_iqs: Dictionary # of id to GDScriptFunctionState
|
||||||
var _pending_tail_iqs: Array # of GDScriptFunctionState
|
var _presence_sinks: Dictionary # of Jid to [[WeakRef, String]]
|
||||||
var _xml_parser := Xml.Parser.new()
|
var _xml_parser := Xml.Parser.new()
|
||||||
|
|
||||||
# todo: Route signals to particular receivers, based on 'from' or 'to'
|
# todo: Route signals to particular receivers, based on 'from' or 'to'
|
||||||
@ -26,16 +28,16 @@ class Connection extends Reference:
|
|||||||
self._id_counter += 1
|
self._id_counter += 1
|
||||||
return hash(self._id_counter)
|
return hash(self._id_counter)
|
||||||
|
|
||||||
func promise_iq(to: String, action: String, payload: String, capture: GDScriptFunctionState):
|
func promise_iq(to: String, type: String, payload: String, capture: GDScriptFunctionState) -> Sums.Promise:
|
||||||
assert(action in ["get", "set"])
|
assert(type in ["get", "set"])
|
||||||
|
|
||||||
var id := self.generate_id()
|
var id := self.generate_id()
|
||||||
if self.stream.put_data(
|
if self.stream.put_data(
|
||||||
"""<iq from='{from}' id='{id}' to='{to}' type='{action}'>{payload}</iq>""".format({
|
"""<iq from='{from}' id='{id}' to='{to}' type='{type}'>{payload}</iq>""".format({
|
||||||
"from": self.jid.xml_escape(),
|
"from": self.jid.xml_escape(),
|
||||||
"id": id,
|
"id": String(id).xml_escape(),
|
||||||
"to": to.xml_escape(),
|
"to": to.xml_escape(),
|
||||||
"action": action,
|
"type": type,
|
||||||
"payload": payload
|
"payload": payload
|
||||||
}).to_utf8()) != OK:
|
}).to_utf8()) != OK:
|
||||||
return Sums.Promise.make_error(ERR_CONNECTION_ERROR)
|
return Sums.Promise.make_error(ERR_CONNECTION_ERROR)
|
||||||
@ -45,6 +47,22 @@ class Connection extends Reference:
|
|||||||
|
|
||||||
return Sums.Promise.from(capture)
|
return Sums.Promise.from(capture)
|
||||||
|
|
||||||
|
func push_presence(to: String, type, payload: String) -> Sums.Result:
|
||||||
|
assert(type == null or type is String)
|
||||||
|
|
||||||
|
var message = Stanza.form_presence(
|
||||||
|
String(self.generate_id()),
|
||||||
|
self.jid, to, type, payload)
|
||||||
|
|
||||||
|
if self.stream.put_data(message.to_utf8()) != OK:
|
||||||
|
return Sums.Result.make_error(ERR_CONNECTION_ERROR)
|
||||||
|
|
||||||
|
return Sums.Result.make_value(null)
|
||||||
|
|
||||||
|
func presence_sink(p_base_jid: String, p_sink: Object, p_signal: String) -> void:
|
||||||
|
self._presence_sinks[p_base_jid] = \
|
||||||
|
self._presence_sinks.get(p_base_jid, []) + [[weakref(p_sink), p_signal]]
|
||||||
|
|
||||||
## Registry of connections used for poking of pending iqs.
|
## Registry of connections used for poking of pending iqs.
|
||||||
var _connections: Array # of WeakRef to Connection
|
var _connections: Array # of WeakRef to Connection
|
||||||
|
|
||||||
@ -73,21 +91,21 @@ func _process_connections() -> void:
|
|||||||
response = null
|
response = null
|
||||||
|
|
||||||
var stanza := connection._xml_parser.take_root()
|
var stanza := connection._xml_parser.take_root()
|
||||||
|
print(stanza.as_string())
|
||||||
|
|
||||||
if stanza.name == "iq":
|
if stanza.name == "iq":
|
||||||
if "to" in stanza.attributes and stanza.attributes["to"] != connection.jid:
|
if "to" in stanza.attributes and stanza.attributes["to"] != connection.jid:
|
||||||
|
# todo: Server errors should not be raised in client.
|
||||||
push_error("Stanza is not addressed to assigned jid")
|
push_error("Stanza is not addressed to assigned jid")
|
||||||
|
|
||||||
if stanza.attributes["type"] in ["result", "error"]:
|
if stanza.attributes["type"] in ["result", "error"]:
|
||||||
var id := int(stanza.attributes["id"]) # todo: Use strings directly instead?
|
var id := int(stanza.attributes["id"]) # todo: Use strings directly instead?
|
||||||
var result = connection._pending_iqs[id].resume(Sums.Result.make_value(stanza))
|
connection._pending_iqs[id].resume(Sums.Result.make_value(stanza))
|
||||||
if result is GDScriptFunctionState and result.is_valid():
|
|
||||||
connection._pending_tail_iqs.push_back(result)
|
|
||||||
# elif result != null:
|
|
||||||
# assert(false, "Ignored result of iq subroutine: " + "%s" % result)
|
|
||||||
var was_present := connection._pending_iqs.erase(id)
|
var was_present := connection._pending_iqs.erase(id)
|
||||||
assert(was_present)
|
assert(was_present)
|
||||||
|
|
||||||
elif stanza.attributes["type"] in ["set", "get"]:
|
elif stanza.attributes["type"] in ["set", "get"]:
|
||||||
|
# todo: Emit in any way?
|
||||||
connection.emit_signal("iq_received", stanza)
|
connection.emit_signal("iq_received", stanza)
|
||||||
|
|
||||||
elif stanza.name == "message":
|
elif stanza.name == "message":
|
||||||
@ -96,17 +114,12 @@ func _process_connections() -> void:
|
|||||||
elif stanza.name == "presence":
|
elif stanza.name == "presence":
|
||||||
connection.emit_signal("presence_received", stanza)
|
connection.emit_signal("presence_received", stanza)
|
||||||
|
|
||||||
var to_remove_tails := PoolIntArray()
|
for base_jid in connection._presence_sinks.keys():
|
||||||
for tail_idx in range(connection._pending_tail_iqs.size()):
|
if not stanza.attributes["from"].begins_with(base_jid):
|
||||||
var result = connection._pending_tail_iqs[tail_idx].resume()
|
continue
|
||||||
if not result is GDScriptFunctionState or not result.is_valid():
|
|
||||||
# assert(result == null, "Ignored result of iq subroutine: " + "%s" % result)
|
|
||||||
to_remove_tails.push_back(tail_idx)
|
|
||||||
else:
|
|
||||||
connection._pending_tail_iqs[tail_idx] = result
|
|
||||||
|
|
||||||
for tail_idx in range(to_remove_tails.size() - 1, 0, -1):
|
for to_emit in connection._presence_sinks[base_jid]:
|
||||||
connection._pending_tail_iqs.remove(to_remove_tails[tail_idx])
|
to_emit[0].emit_signal(to_emit[1], stanza)
|
||||||
|
|
||||||
## Collect dropped connections.
|
## Collect dropped connections.
|
||||||
for idx in range(to_remove.size() - 1, 0, -1):
|
for idx in range(to_remove.size() - 1, 0, -1):
|
||||||
|
@ -6,10 +6,40 @@ class_name MucService
|
|||||||
class MucRoom extends Reference:
|
class MucRoom extends Reference:
|
||||||
var jid: String
|
var jid: String
|
||||||
var name: String
|
var name: String
|
||||||
|
var members: Dictionary # nick to MucMember
|
||||||
|
|
||||||
|
signal presence_received(presence)
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
if self.connect("presence_received", self, "_presence_received") != OK:
|
||||||
|
assert(false)
|
||||||
|
|
||||||
|
func _presence_received(presence) -> void:
|
||||||
|
if presence.children.size() == 0:
|
||||||
|
return
|
||||||
|
var x = presence.get_named_child_element("x")
|
||||||
|
if x.name != "x" and x.attributes["xmlns"] != "http://jabber.org/protocol/muc#user":
|
||||||
|
## Member information came.
|
||||||
|
var item = x.get_named_child_element("item")
|
||||||
|
var nick = presence.attributes["from"].rsplit("/").pop_front()
|
||||||
|
var member = MucMember.new()
|
||||||
|
member.jid = presence.attributes["from"]
|
||||||
|
member.nick = nick
|
||||||
|
member.role = item.attributes["role"]
|
||||||
|
member.affiliation = item.attributes["affiliation"]
|
||||||
|
# member.room = self
|
||||||
|
members[nick] = member
|
||||||
|
|
||||||
func as_string() -> String:
|
func as_string() -> String:
|
||||||
return "MucRoom \"%s\", Jid: \"%s\"" % [name, jid]
|
return "MucRoom \"%s\", Jid: \"%s\"" % [name, jid]
|
||||||
|
|
||||||
|
class MucMember extends Reference:
|
||||||
|
var jid: String
|
||||||
|
var nick: String
|
||||||
|
var role: String
|
||||||
|
# var room: MucRoom
|
||||||
|
var affiliation: String
|
||||||
|
|
||||||
var jid: String
|
var jid: String
|
||||||
|
|
||||||
var _connection: Connections.Connection
|
var _connection: Connections.Connection
|
||||||
@ -41,7 +71,7 @@ func request_rooms() -> Sums.Promise:
|
|||||||
|
|
||||||
return self._rooms_promise
|
return self._rooms_promise
|
||||||
|
|
||||||
func _iq_rooms():
|
func _iq_rooms() -> Sums.Result:
|
||||||
var response = yield()
|
var response = yield()
|
||||||
if not response.is_ok:
|
if not response.is_ok:
|
||||||
return response
|
return response
|
||||||
@ -54,8 +84,42 @@ func _iq_rooms():
|
|||||||
for item in query.value.children:
|
for item in query.value.children:
|
||||||
if item.is_element() and item.name == "item":
|
if item.is_element() and item.name == "item":
|
||||||
var muc_room := MucRoom.new()
|
var muc_room := MucRoom.new()
|
||||||
|
_connection.presence_sink(muc_room.jid, muc_room, "presence_received")
|
||||||
muc_room.jid = item.attributes["jid"]
|
muc_room.jid = item.attributes["jid"]
|
||||||
muc_room.name = item.attributes["name"]
|
muc_room.name = item.attributes["name"]
|
||||||
rooms.push_back(muc_room)
|
rooms.push_back(muc_room)
|
||||||
|
|
||||||
return Sums.Result.make_value(rooms)
|
return Sums.Result.make_value(rooms)
|
||||||
|
|
||||||
|
## Returns MucMemeber that is registered on behalf on user.
|
||||||
|
func join_room(room: MucRoom, nick: String) -> Sums.Promise:
|
||||||
|
# todo: https://xmpp.org/extensions/xep-0045.html#reservednick
|
||||||
|
return Sums.Promise.from(_join_room(room, nick))
|
||||||
|
|
||||||
|
func _join_room(room: MucRoom, nick: String) -> Sums.Result:
|
||||||
|
var member_jid = room.jid + '/' + nick
|
||||||
|
var result = _connection.push_presence(member_jid, null,
|
||||||
|
"<x xmlns='http://jabber.org/protocol/muc'/>")
|
||||||
|
if not result.is_ok:
|
||||||
|
return result
|
||||||
|
|
||||||
|
var response = Stanza.presence_result(yield(room, "presence_received"))
|
||||||
|
if not response.is_ok:
|
||||||
|
return response
|
||||||
|
|
||||||
|
while true:
|
||||||
|
response = Stanza.presence_result(yield(room, "presence_received"))
|
||||||
|
if not response.is_ok:
|
||||||
|
return response
|
||||||
|
|
||||||
|
if response.value.attributes["from"] == member_jid:
|
||||||
|
if response.value.children.size() == 0:
|
||||||
|
continue
|
||||||
|
var x = response.value.children[0]
|
||||||
|
if x.name != "x" and x.attributes["xmlns"] != "http://jabber.org/protocol/muc#user":
|
||||||
|
continue
|
||||||
|
for child in x.children:
|
||||||
|
if child.is_element() and child.name == "status" and child.attrbiutes["code"] == "110":
|
||||||
|
return Sums.Result.make_value(room.members[nick])
|
||||||
|
|
||||||
|
return Sums.Result.make_error(null)
|
||||||
|
@ -17,8 +17,7 @@ func _service_discovery(connection: Connections.Connection) -> Sums.Result:
|
|||||||
if not query.is_ok:
|
if not query.is_ok:
|
||||||
return query.value
|
return query.value
|
||||||
|
|
||||||
var feature_promises := Array()
|
var poly_services := Array()
|
||||||
var poly_services := Dictionary() # of Jid to PolyService
|
|
||||||
for item in query.value.children:
|
for item in query.value.children:
|
||||||
if not item.is_element() or item.name != "item":
|
if not item.is_element() or item.name != "item":
|
||||||
continue
|
continue
|
||||||
@ -27,30 +26,28 @@ func _service_discovery(connection: Connections.Connection) -> Sums.Result:
|
|||||||
poly_service.jid = item.attributes["jid"]
|
poly_service.jid = item.attributes["jid"]
|
||||||
if "name" in item.attributes:
|
if "name" in item.attributes:
|
||||||
poly_service.name = item.attributes["name"]
|
poly_service.name = item.attributes["name"]
|
||||||
poly_services[item.attributes["jid"]] = poly_service
|
|
||||||
|
|
||||||
feature_promises.push_back(connection.promise_iq(
|
poly_services.push_back(poly_service)
|
||||||
|
|
||||||
|
var feature_promise = connection.promise_iq(
|
||||||
item.attributes["jid"], "get",
|
item.attributes["jid"], "get",
|
||||||
Stanza.disco_info_queury,
|
Stanza.disco_info_queury,
|
||||||
Stanza.yield_as_is()))
|
Stanza.yield_as_is())
|
||||||
|
|
||||||
while not Sums.are_promises_done(feature_promises):
|
if not feature_promise.is_done:
|
||||||
yield()
|
yield(feature_promise, "done")
|
||||||
|
|
||||||
# todo: Allow partial success.
|
# todo: Propagate error if it's unrelated to service discovery itself.
|
||||||
if not Sums.are_promises_ok(feature_promises):
|
if feature_promise.is_ok:
|
||||||
return Sums.Result.make_error(Sums.collect_promise_errors(feature_promises))
|
# If features arrived, - populate services.
|
||||||
|
var jid = feature_promise.value.attributes["from"]
|
||||||
|
|
||||||
for feature_response in Sums.collect_promise_values(feature_promises):
|
var features := Stanza.unwrap_query_result(feature_promise.value)
|
||||||
var jid = feature_response.attributes["from"]
|
|
||||||
|
|
||||||
var features := Stanza.unwrap_query_result(feature_response)
|
|
||||||
if not features.is_ok:
|
if not features.is_ok:
|
||||||
# todo: Signal the error.
|
# todo: Propagate the error.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var poly_service = poly_services[jid]
|
|
||||||
poly_service.muc = load("res://scenes/MucService.gd").new().try_init(
|
poly_service.muc = load("res://scenes/MucService.gd").new().try_init(
|
||||||
connection, jid, features.value)
|
connection, jid, features.value)
|
||||||
|
|
||||||
return Sums.Result.make_value(poly_services.values())
|
return Sums.Result.make_value(poly_services)
|
||||||
|
@ -20,3 +20,26 @@ static func unwrap_query_result(iq: Xml.XmlElement) -> Sums.Result:
|
|||||||
return Sums.Result.make_value(child)
|
return Sums.Result.make_value(child)
|
||||||
|
|
||||||
return Sums.Result.make_error(ERR_INVALID_DATA)
|
return Sums.Result.make_error(ERR_INVALID_DATA)
|
||||||
|
|
||||||
|
static func form_presence(id: String, from: String, to: String, type, payload: String) -> String:
|
||||||
|
if type != null:
|
||||||
|
return """<presence from='{from}' id='{id}' to='{to}' type='{type}'>{payload}</presence>""".format({
|
||||||
|
"from": from.xml_escape(),
|
||||||
|
"id": id.xml_escape(),
|
||||||
|
"to": to.xml_escape(),
|
||||||
|
"type": type,
|
||||||
|
"payload": payload
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return """<presence from='{from}' id='{id}' to='{to}'>{payload}</presence>""".format({
|
||||||
|
"from": from.xml_escape(),
|
||||||
|
"id": id.xml_escape(),
|
||||||
|
"to": to.xml_escape(),
|
||||||
|
"payload": payload
|
||||||
|
})
|
||||||
|
|
||||||
|
static func presence_result(stanza: Xml.XmlElement) -> Sums.Result:
|
||||||
|
if "type" in stanza.attributes and stanza.attributes["type"] == "error":
|
||||||
|
return Sums.Result.make_error(stanza)
|
||||||
|
else:
|
||||||
|
return Sums.Result.make_value(stanza)
|
||||||
|
@ -41,14 +41,15 @@ class Promise extends Reference:
|
|||||||
var result := Promise.new()
|
var result := Promise.new()
|
||||||
result.is_done = true
|
result.is_done = true
|
||||||
result.is_ok = false
|
result.is_ok = false
|
||||||
|
# todo: Proper error object / convention.
|
||||||
result.value = [error, get_stack().slice(1, -1)]
|
result.value = [error, get_stack().slice(1, -1)]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# todo: Use Error class value for erroneous state instead.
|
func _result_arrived(p_result: Result) -> void:
|
||||||
func _result_arrived(p_result) -> void:
|
assert(p_result != null)
|
||||||
self.is_done = true
|
self.is_done = true
|
||||||
self.is_ok = true
|
self.is_ok = p_result.is_ok
|
||||||
self.value = p_result
|
self.value = p_result.value
|
||||||
emit_signal("done")
|
emit_signal("done")
|
||||||
|
|
||||||
static func are_promises_done(promises: Array) -> bool:
|
static func are_promises_done(promises: Array) -> bool:
|
||||||
@ -71,11 +72,7 @@ static func collect_promise_values(promises: Array) -> Array:
|
|||||||
for promise in promises:
|
for promise in promises:
|
||||||
assert(promise is Promise)
|
assert(promise is Promise)
|
||||||
assert(promise.is_done)
|
assert(promise.is_done)
|
||||||
# todo: Don't do this.
|
result.push_back(promise.value)
|
||||||
if promise.value is Result:
|
|
||||||
assert(promise.value.is_ok)
|
|
||||||
result.push_back(promise.value.value)
|
|
||||||
else: result.push_back(promise.value)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
static func collect_promise_errors(promises: Array) -> Array:
|
static func collect_promise_errors(promises: Array) -> Array:
|
||||||
@ -85,7 +82,4 @@ static func collect_promise_errors(promises: Array) -> Array:
|
|||||||
assert(promise.is_done)
|
assert(promise.is_done)
|
||||||
if not promise.is_ok:
|
if not promise.is_ok:
|
||||||
result.push_back(promise.value)
|
result.push_back(promise.value)
|
||||||
# todo: Don't do this.
|
|
||||||
elif promise.value is Result and not promise.value.is_ok:
|
|
||||||
result.push_back(promise.value.value)
|
|
||||||
return result
|
return result
|
||||||
|
@ -12,6 +12,13 @@ class XmlElement extends XmlNode:
|
|||||||
|
|
||||||
func is_element() -> bool: return true
|
func is_element() -> bool: return true
|
||||||
|
|
||||||
|
func get_named_child_element(p_name: String) -> XmlElement:
|
||||||
|
for idx in range(self.children.size()):
|
||||||
|
var child := self.children[idx] as XmlElement
|
||||||
|
if child != null and child.name == p_name:
|
||||||
|
return child
|
||||||
|
return null
|
||||||
|
|
||||||
func take_named_child_element(p_name: String) -> XmlElement:
|
func take_named_child_element(p_name: String) -> XmlElement:
|
||||||
for idx in range(self.children.size()):
|
for idx in range(self.children.size()):
|
||||||
var child := self.children[idx] as XmlElement
|
var child := self.children[idx] as XmlElement
|
||||||
|
Loading…
Reference in New Issue
Block a user