mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-01 06:32:14 +00:00
80f3af0690
Introduce a new viewset that lets the roomkit start a room even when no WebRTC participant has joined yet. This is a first entry point that will be extended over time with more actions a roomkit needs to be able to trigger. Known limitations: * The responsibility around SIP rules is currently split between the telephony feature and the roomkit one. This may need a refactor later on to consolidate ownership in a single place. * The default throttle might be too low for production usage and will likely need to be revisited.
153 lines
4.8 KiB
Python
153 lines
4.8 KiB
Python
"""Telephony service for managing SIP dispatch rules for room access."""
|
|
|
|
from logging import getLogger
|
|
|
|
from asgiref.sync import async_to_sync
|
|
from livekit.api import TwirpError, TwirpErrorCode
|
|
from livekit.protocol.sip import (
|
|
CreateSIPDispatchRuleRequest,
|
|
DeleteSIPDispatchRuleRequest,
|
|
ListSIPDispatchRuleRequest,
|
|
SIPDispatchRule,
|
|
SIPDispatchRuleDirect,
|
|
)
|
|
|
|
from core import utils
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
class TelephonyException(Exception):
|
|
"""Exception raised when telephony operations fail."""
|
|
|
|
|
|
class DispatchRuleConflictError(TelephonyException):
|
|
"""Raised when a dispatch rule already exists for the same routing criteria."""
|
|
|
|
|
|
class TelephonyService:
|
|
"""Service for managing participant access through the telephony system (SIP)."""
|
|
|
|
def _rule_name(self, room_id):
|
|
"""Generate the rule name for a room based on its ID."""
|
|
return f"SIP_{str(room_id)}"
|
|
|
|
@async_to_sync
|
|
async def create_dispatch_rule(self, room):
|
|
"""Create a SIP inbound dispatch rule for direct room routing.
|
|
|
|
Configures telephony to route incoming SIP calls directly to the specified room
|
|
using the room's ID and PIN code for authentication.
|
|
"""
|
|
|
|
direct_rule = SIPDispatchRule(
|
|
dispatch_rule_direct=SIPDispatchRuleDirect(
|
|
room_name=str(room.pk), pin=str(room.pin_code)
|
|
)
|
|
)
|
|
|
|
request = CreateSIPDispatchRuleRequest(
|
|
rule=direct_rule, name=self._rule_name(room.pk)
|
|
)
|
|
|
|
lkapi = utils.create_livekit_client()
|
|
|
|
try:
|
|
await lkapi.sip.create_sip_dispatch_rule(create=request)
|
|
except TwirpError as e:
|
|
if e.code == TwirpErrorCode.ALREADY_EXISTS:
|
|
raise DispatchRuleConflictError("Dispatch rule already exists") from e
|
|
logger.exception(
|
|
"Unexpected error creating dispatch rule for room %s", room.id
|
|
)
|
|
raise TelephonyException("Could not create dispatch rule") from e
|
|
|
|
finally:
|
|
await lkapi.aclose()
|
|
|
|
async def _list_dispatch_rules_ids(self, room_id):
|
|
"""List SIP dispatch rule IDs for a specific room.
|
|
|
|
Fetches all existing SIP dispatch rules and filters them by room name
|
|
since LiveKit API doesn't support server-side filtering by 'room_name'.
|
|
This approach is acceptable for moderate scale but may need refactoring
|
|
for high-volume scenarios.
|
|
|
|
Note:
|
|
Feature request for server-side filtering: livekit/sip#405
|
|
"""
|
|
|
|
lkapi = utils.create_livekit_client()
|
|
|
|
try:
|
|
existing_rules = await lkapi.sip.list_sip_dispatch_rule(
|
|
list=ListSIPDispatchRuleRequest()
|
|
)
|
|
except TwirpError as e:
|
|
logger.exception("Failed to list dispatch rules for room %s", room_id)
|
|
raise TelephonyException("Could not list dispatch rules") from e
|
|
finally:
|
|
await lkapi.aclose()
|
|
|
|
if not existing_rules or not existing_rules.items:
|
|
return []
|
|
|
|
rule_name = self._rule_name(room_id)
|
|
|
|
return [
|
|
existing_rule.sip_dispatch_rule_id
|
|
for existing_rule in existing_rules.items
|
|
if existing_rule.name == rule_name
|
|
]
|
|
|
|
@async_to_sync
|
|
async def has_dispatch_rule(self, room_id):
|
|
"""Check whether at least one dispatch rule exists for a specific room."""
|
|
return bool(await self._list_dispatch_rules_ids(room_id))
|
|
|
|
def ensure_dispatch_rule(self, room):
|
|
"""Create the SIP dispatch rule for a room if it does not already exist.
|
|
|
|
Returns:
|
|
bool: True if a rule was created, False if it already existed.
|
|
"""
|
|
|
|
if self.has_dispatch_rule(room.pk):
|
|
return False
|
|
|
|
try:
|
|
self.create_dispatch_rule(room)
|
|
except DispatchRuleConflictError:
|
|
return False
|
|
|
|
return True
|
|
|
|
@async_to_sync
|
|
async def delete_dispatch_rule(self, room_id):
|
|
"""Delete all SIP inbound dispatch rules associated with a specific room."""
|
|
|
|
rules_ids = await self._list_dispatch_rules_ids(room_id)
|
|
|
|
if not rules_ids:
|
|
logger.info("No dispatch rules found for room %s", room_id)
|
|
return False
|
|
|
|
if len(rules_ids) > 1:
|
|
logger.error("Multiple dispatch rules found for room %s", room_id)
|
|
|
|
lkapi = utils.create_livekit_client()
|
|
try:
|
|
for rule_id in rules_ids:
|
|
await lkapi.sip.delete_sip_dispatch_rule(
|
|
delete=DeleteSIPDispatchRuleRequest(sip_dispatch_rule_id=rule_id)
|
|
)
|
|
|
|
return True
|
|
|
|
except TwirpError as e:
|
|
logger.exception("Failed to delete dispatch rules for room %s", room_id)
|
|
raise TelephonyException("Could not delete dispatch rules") from e
|
|
|
|
finally:
|
|
await lkapi.aclose()
|