component conferences, plugin XEP-0167, XEP-0298: add stream user metadata:

A/V conference now adds user metadata about the stream it is forwarding through XEP-0298.
This is parsed and added to metadata during confirmation on client side.

rel 448
This commit is contained in:
Goffi 2024-08-06 23:43:11 +02:00
commit 08e1409925
4 changed files with 86 additions and 23 deletions

View file

@ -236,10 +236,22 @@ class Conference:
async def a_on_offer(self, data: dict) -> None:
client: SatXMPPEntity = self.client.get_virtual_client(self.session["local_jid"])
call_data = {"sdp": data["sdp"]}
try:
# FIXME: This assume that all username are coming from XMPP, "source" should
# be used instead to be sure to match XMPP users, and other ones should use
# a non "xmpp:" URL scheme.
user_jid = jid.JID(data["username"])
if not user_jid.user:
raise ValueError("Missing user part.")
except Exception as e:
log.warning("Username is not a JID: {data['username']=}, {e}")
else:
call_data["metadata"] = {"user": user_jid}
sid = await self._rtp.call_start(
client,
self.session["peer_jid"],
{"sdp": data["sdp"]},
call_data,
session_id=f"{self.client_id}-{data['id']}",
)
session = self._j.get_session(client, sid)
@ -547,7 +559,6 @@ class ConferencesComponent:
log.error(f"Can't run Galene process: {failure_.value}")
@implementer(iwokkel.IDisco)
class ConferencesHandler(XMPPHandler):

View file

@ -1539,7 +1539,7 @@ class XEP_0060(object):
try:
return {
jid.JID(s["jid"]): s["subscription"]
for s in subscriptions_elt.elements((pubsub.NS_PUBSUB, "subscription"))
for s in subscriptions_elt.elements(pubsub.NS_PUBSUB, "subscription")
}
except KeyError:
raise ValueError(

View file

@ -41,7 +41,7 @@ from .constants import (
NS_JINGLE_RTP_AUDIO,
NS_JINGLE_RTP_INFO,
NS_JINGLE_RTP_VIDEO,
NS_AV_CONFERENCES
NS_AV_CONFERENCES,
)
@ -419,17 +419,22 @@ class XEP_0167(BaseApplicationHandler):
session["call_type"] = call_type
cancellable_deferred = session.setdefault("cancellable_deferred", [])
action_extra = {
"session_id": session["id"],
"from_jid": peer_jid.full(),
"type": C.META_TYPE_CALL,
"sub_type": call_type,
}
try:
action_extra["metadata"] = {"user": session["metadata"]["peer_user"].full()}
except KeyError:
pass
dialog_d = xml_tools.defer_dialog(
self.host,
_(CONFIRM).format(peer=peer_jid.userhost(), call_type=call_type),
_(CONFIRM_TITLE),
action_extra={
"session_id": session["id"],
"from_jid": peer_jid.full(),
"type": C.META_TYPE_CALL,
"sub_type": call_type,
},
action_extra=action_extra,
security_limit=SECURITY_LIMIT,
profile=client.profile,
)
@ -596,26 +601,22 @@ class XEP_0167(BaseApplicationHandler):
answer_sdp_d.addTimeout(2 * 60, reactor)
call_setup = session.get("call_setup_cb")
call_data = {
"role": session["role"],
"sdp": sdp,
}
if call_setup is None:
self.host.bridge.call_setup(
session["id"],
data_format.serialise(
{
"role": session["role"],
"sdp": sdp,
}
),
data_format.serialise(call_data),
client.profile,
)
else:
await call_setup(
client,
session,
{
"role": session["role"],
"sdp": sdp,
},
call_data,
)
answer_sdp = await answer_sdp_d
@ -681,7 +682,7 @@ class XEP_0167(BaseApplicationHandler):
content_data = session["contents"][content_name]
application_data = content_data["application_data"]
if action == self._j.A_PREPARE_CONFIRMATION:
session["metadata"] = {}
session.setdefault("metadata", {})
session.setdefault("peer_metadata", {})
try:
media = application_data["media"] = desc_elt["media"]

View file

@ -20,6 +20,7 @@
from urllib.parse import quote, unquote
from twisted.words.protocols.jabber import jid
from twisted.words.xish import domish
from libervia.backend.core.core_types import SatXMPPEntity
from libervia.backend.core.i18n import _
from libervia.backend.core.constants import Const as C
from libervia.backend.core.log import getLogger
@ -27,6 +28,8 @@ from wokkel import disco, iwokkel
from zope.interface import implementer
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
from libervia.backend.plugins.plugin_xep_0166 import XEP_0166
log = getLogger(__name__)
@ -36,7 +39,7 @@ PLUGIN_INFO = {
C.PI_TYPE: "XEP",
C.PI_MODES: C.PLUG_MODE_BOTH,
C.PI_PROTOCOLS: [],
C.PI_DEPENDENCIES: [],
C.PI_DEPENDENCIES: ["XEP-0167"],
C.PI_MAIN: "XEP_0298",
C.PI_HANDLER: "yes",
C.PI_DESCRIPTION: _(
@ -55,10 +58,58 @@ class XEP_0298:
def __init__(self, host):
log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization")
self.host = host
self._j: XEP_0166 = host.plugins["XEP-0166"]
host.trigger.add(
"XEP-0167_jingle_session_init", self._jingle_session_init_trigger
)
host.trigger.add("XEP-0167_jingle_handler", self._jingle_handler_trigger)
def get_handler(self, client):
return CoinHandler(self)
def _jingle_session_init_trigger(
self,
client: SatXMPPEntity,
session: dict,
content_name: str,
media: str,
media_data: dict,
desc_elt: domish.Element,
) -> None:
"""Check for the presence of "user" metadata, and add relevant elements."""
if client.profile == "conferences":
if content_name != next(iter(session["contents"].keys())):
# We only apply metadata for the first content, as it is global.
return
try:
user = session["metadata"]["user"]
except KeyError:
return
jingle_elt = session["jingle_elt"]
conference_info_elt = self.add_conference_info(jingle_elt, True)
self.add_user(conference_info_elt, user)
async def _jingle_handler_trigger(
self,
client: SatXMPPEntity,
action: str,
session: dict,
content_name: str,
desc_elt: domish.Element,
) -> None:
# this is a session metadata, so we only generate it on the first content
if action == self._j.A_PREPARE_CONFIRMATION and content_name == next(
iter(session["contents"])
):
jingle_elt = session["jingle_elt"]
infos = self.parse(jingle_elt)
try:
user = infos["info"]["users"][0]
except (KeyError, IndexError):
pass
else:
session.setdefault("metadata", {})["peer_user"] = user
def add_conference_info(
self, jingle_elt: domish.Element, is_focus: bool = False, **kwargs
) -> domish.Element:
@ -104,7 +155,7 @@ class XEP_0298:
"""
try:
conference_info_elt = next(
jingle_elt.elements((NS_CONFERENCE_INFO, "conference-info"))
jingle_elt.elements(NS_CONFERENCE_INFO, "conference-info")
)
except StopIteration:
return {}