diff --git a/libervia/backend/plugins/plugin_xep_0353.py b/libervia/backend/plugins/plugin_xep_0353.py index c7e9a170..29e311c5 100644 --- a/libervia/backend/plugins/plugin_xep_0353.py +++ b/libervia/backend/plugins/plugin_xep_0353.py @@ -86,9 +86,10 @@ class XEP_0353: "XEP-0166_initiate_elt_built", self, self._on_initiate_trigger, - # this plugin set the resource, we want it to happen first so other triggers - # can get the full peer JID - priority=host.trigger.MAX_PRIORITY, + # must run last: it takes over the jingle IQ sending (sent in the + # background once the proposal is accepted), after the other + # triggers have built the IQ + priority=host.trigger.MIN_PRIORITY, ) host.trigger.add_with_check( "XEP-0166_terminate", @@ -176,23 +177,37 @@ class XEP_0353: # FIXME: let's application decide timeout? response_d.addTimeout(2 * 60, reactor) client._xep_0353_pending_sessions[session["id"]] = response_d + await client.send_message_data(mess_data) + # the jingle IQ is sent in the background once the proposal is accepted: returning + # False takes over the sending, so the session id is available right away and the + # session can be cancelled while the proposal is pending + defer.ensureDeferred( + self._send_jingle_after_accept(client, session, iq_elt, response_d) + ) + return False + + async def _send_jingle_after_accept( + self, + client: SatXMPPEntity, + session: dict, + iq_elt: domish.Element, + response_d: defer.Deferred, + ) -> None: + """Send the jingle session-initiate once the proposal is accepted.""" + session_id = session["id"] + e = None try: - await client.send_message_data(mess_data) accepting_jid = await response_d - except defer.TimeoutError: + except defer.TimeoutError as timeout_e: log.warning( _("Message initiation with {peer_jid} timed out").format( - peer_jid=peer_jid + peer_jid=session["peer_jid"] ) ) - except exceptions.CancelError as e: - for content in session["contents"].values(): - await content["application"].handler.jingle_preflight_cancel( - client, session, e - ) - - self._j.delete_session(client, session["id"]) - return False + e = exceptions.CancelError(timeout_e) + e.reason = "timeout" + except (exceptions.CancelError, RetractException) as cancel_e: + e = cancel_e else: if iq_elt["to"] != accepting_jid.userhost(): raise exceptions.InternalError( @@ -202,9 +217,24 @@ class XEP_0353: ) iq_elt["to"] = accepting_jid.full() session["peer_jid"] = accepting_jid + if self._j.get_session(client, session_id) is None: + # the session was deleted while waiting for the acceptance + return + try: + await iq_elt.send() + except Exception: + log.exception("Error while sending jingle stanza") + self._j.delete_session(client, session_id) + raise finally: - del client._xep_0353_pending_sessions[session["id"]] - return True + client._xep_0353_pending_sessions.pop(session_id, None) + if e is not None: + # notify the application of the cancelled initiation, then clean the session + for content in session["contents"].values(): + await content["application"].handler.jingle_preflight_cancel( + client, session, e + ) + self._j.delete_session(client, session_id) def _terminate_trigger( self, client: SatXMPPEntity, session: dict, reason_elt: domish.Element