Increase platform abstraction cohesion

This commit is contained in:
2026-03-06 17:47:58 +00:00
parent 438e561da0
commit 8c091b1e6d
55 changed files with 6555 additions and 440 deletions

View File

@@ -141,10 +141,14 @@ class XMPPComponent(ComponentXMPP):
self._reconnect_task = None
self._reconnect_delay_seconds = 1.0
self._reconnect_delay_max_seconds = 30.0
self._connect_inflight = False
self._session_live = False
self.log = logs.get_logger("XMPP")
super().__init__(jid, secret, server, port)
# Use one reconnect strategy (our backoff loop) to avoid reconnect churn.
self.auto_reconnect = False
# Register chat state plugins
register_stanza_plugin(Message, Active)
register_stanza_plugin(Message, Composing)
@@ -178,6 +182,21 @@ class XMPPComponent(ComponentXMPP):
self.add_event_handler("chatstate_inactive", self.on_chatstate_inactive)
self.add_event_handler("chatstate_gone", self.on_chatstate_gone)
def _user_xmpp_domain(self):
domain = str(getattr(settings, "XMPP_USER_DOMAIN", "") or "").strip()
if domain:
return domain
component_jid = str(getattr(settings, "XMPP_JID", "") or "").strip()
if "." in component_jid:
return component_jid.split(".", 1)[1]
configured_domain = str(getattr(settings, "DOMAIN", "") or "").strip()
if configured_domain:
return configured_domain
return str(getattr(settings, "XMPP_ADDRESS", "") or "").strip()
def _user_jid(self, username):
return f"{username}@{self._user_xmpp_domain()}"
async def enable_carbons(self):
"""Enable XMPP Message Carbons (XEP-0280)"""
try:
@@ -827,25 +846,33 @@ class XMPPComponent(ComponentXMPP):
async def session_start(self, *args):
self.log.info("XMPP session started")
self._session_live = True
self._connect_inflight = False
self._reconnect_delay_seconds = 1.0
if self._reconnect_task and not self._reconnect_task.done():
self._reconnect_task.cancel()
self._reconnect_task = None
await self.enable_carbons()
# This client connects as an external component, not a user client;
# XEP-0280 (carbons) is client-scoped and not valid here.
self.log.debug("Skipping carbons enable for component session")
async def _reconnect_loop(self):
try:
while True:
delay = float(self._reconnect_delay_seconds)
await asyncio.sleep(delay)
if self._session_live or self._connect_inflight:
return
try:
self.log.info("XMPP reconnect attempt delay_s=%.1f", delay)
self._connect_inflight = True
connected = self.connect()
if connected is False:
raise RuntimeError("connect returned false")
return
except Exception as exc:
self.log.warning("XMPP reconnect attempt failed: %s", exc)
self._connect_inflight = False
self._reconnect_delay_seconds = min(
self._reconnect_delay_max_seconds,
max(1.0, float(self._reconnect_delay_seconds) * 2.0),
@@ -853,6 +880,8 @@ class XMPPComponent(ComponentXMPP):
except asyncio.CancelledError:
return
finally:
if not self._session_live:
self._connect_inflight = False
self._reconnect_task = None
def _schedule_reconnect(self):
@@ -864,6 +893,8 @@ class XMPPComponent(ComponentXMPP):
"""
Handles XMPP disconnection and triggers a reconnect loop.
"""
self._session_live = False
self._connect_inflight = False
self.log.warning(
"XMPP disconnected, scheduling reconnect attempt in %.1fs",
float(self._reconnect_delay_seconds),
@@ -1576,7 +1607,7 @@ class XMPPComponent(ComponentXMPP):
f"{person_identifier.person.name.lower()}|"
f"{person_identifier.service}@{settings.XMPP_JID}"
)
recipient_jid = f"{user.username}@{settings.XMPP_ADDRESS}"
recipient_jid = self._user_jid(user.username)
await self.send_xmpp_reaction(
recipient_jid,
sender_jid,
@@ -1625,7 +1656,7 @@ class XMPPComponent(ComponentXMPP):
f"{person_identifier.person.name.lower()}|"
f"{person_identifier.service}@{settings.XMPP_JID}"
)
recipient_jid = f"{user.username}@{settings.XMPP_ADDRESS}"
recipient_jid = self._user_jid(user.username)
await self.send_chat_state(recipient_jid, sender_jid, started)
async def send_from_external(
@@ -1640,7 +1671,7 @@ class XMPPComponent(ComponentXMPP):
"""Handles sending XMPP messages with text and attachments."""
sender_jid = f"{person_identifier.person.name.lower()}|{person_identifier.service}@{settings.XMPP_JID}"
recipient_jid = f"{person_identifier.user.username}@{settings.XMPP_ADDRESS}"
recipient_jid = self._user_jid(person_identifier.user.username)
if is_outgoing_message:
xmpp_id = await self.send_xmpp_message(
recipient_jid,
@@ -1767,22 +1798,45 @@ class XMPPComponent(ComponentXMPP):
class XMPPClient(ClientBase):
def __init__(self, ur, *args, **kwargs):
super().__init__(ur, *args, **kwargs)
self.client = XMPPComponent(
ur,
jid=settings.XMPP_JID,
secret=settings.XMPP_SECRET,
server=settings.XMPP_ADDRESS,
port=settings.XMPP_PORT,
)
self._enabled = True
self.client = None
jid = str(getattr(settings, "XMPP_JID", "") or "").strip()
secret = str(getattr(settings, "XMPP_SECRET", "") or "").strip()
server = str(getattr(settings, "XMPP_ADDRESS", "") or "").strip()
port = int(getattr(settings, "XMPP_PORT", 8888) or 8888)
missing = []
if not jid:
missing.append("XMPP_JID")
if not secret:
missing.append("XMPP_SECRET")
if not server:
missing.append("XMPP_ADDRESS")
if missing:
self._enabled = False
self.log.warning(
"XMPP client disabled due to missing configuration: %s",
", ".join(missing),
)
self.client.register_plugin("xep_0030") # Service Discovery
self.client.register_plugin("xep_0004") # Data Forms
self.client.register_plugin("xep_0060") # PubSub
self.client.register_plugin("xep_0199") # XMPP Ping
self.client.register_plugin("xep_0085") # Chat State Notifications
self.client.register_plugin("xep_0363") # HTTP File Upload
if self._enabled:
self.client = XMPPComponent(
ur,
jid=jid,
secret=secret,
server=server,
port=port,
)
self.client.register_plugin("xep_0030") # Service Discovery
self.client.register_plugin("xep_0004") # Data Forms
self.client.register_plugin("xep_0060") # PubSub
self.client.register_plugin("xep_0199") # XMPP Ping
self.client.register_plugin("xep_0085") # Chat State Notifications
self.client.register_plugin("xep_0363") # HTTP File Upload
def start(self):
if not self._enabled or self.client is None:
return
self.log.info("XMPP client starting...")
# ensure slixmpp uses the same asyncio loop as the router
@@ -1791,7 +1845,11 @@ class XMPPClient(ClientBase):
self.client.connect()
async def start_typing_for_person(self, user, person_identifier):
if self.client is None:
return
await self.client.send_typing_for_person(user, person_identifier, True)
async def stop_typing_for_person(self, user, person_identifier):
if self.client is None:
return
await self.client.send_typing_for_person(user, person_identifier, False)