From beef7d526b01d6a47e7766d86fad09818edef7a5 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 May 2024 18:09:05 +1200 Subject: [PATCH] support timeout for intiating calls, and set default to 15 seconds --- public/call.html | 13 +++++++++++-- src/audio_call_manager.py | 18 +++++++++++++----- web.py | 9 ++++++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/public/call.html b/public/call.html index 41054d2..126b641 100644 --- a/public/call.html +++ b/public/call.html @@ -344,7 +344,11 @@ try { // initiate call - const response = await axios.get(`/api/v1/calls/initiate/${destinationHash}`); + const response = await axios.get(`/api/v1/calls/initiate/${destinationHash}`, { + params: { + timeout: 15, // how long to attempt to initiate call + }, + }); // get call hash from response const hash = response.data.hash; @@ -372,8 +376,13 @@ alert("timed out attempting to join call"); } catch(e) { - alert("failed to initiate call"); + console.log(e); + + // show error message from response, or fallback to default + const message = e.response?.data?.message ?? "failed to initiate call"; + alert(message); + } finally { // hide loading this.isInitiatingCall = false; diff --git a/src/audio_call_manager.py b/src/audio_call_manager.py index 2be9e95..e0b1718 100644 --- a/src/audio_call_manager.py +++ b/src/audio_call_manager.py @@ -1,4 +1,5 @@ import asyncio +import time from typing import List import RNS @@ -130,16 +131,23 @@ class AudioCallManager: self.audio_calls.remove(audio_call) # attempts to initiate a call to the provided destination and returns the link hash on success - # FIXME: implement timeout. at the moment, it loops forever if no path is found - async def initiate(self, destination_hash: bytes) -> bytes: + async def initiate(self, destination_hash: bytes, timeout_seconds: int = 15) -> bytes | None: - # wait until we have a path to the destination - # FIXME: implement timeout instead of looping forever + # check if we have a path to the destination if not RNS.Transport.has_path(destination_hash): + + # we don't have a path, so we need to request it RNS.Transport.request_path(destination_hash) - while not RNS.Transport.has_path(destination_hash): + + # wait until we have a path, or give up after the configured timeout + timeout_after_seconds = time.time() + timeout_seconds + while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after_seconds: await asyncio.sleep(0.1) + # if we still don't have a path, we can't establish a link, so bail out + if not RNS.Transport.has_path(destination_hash): + return None + # create outbound destination to initiate audio calls server_identity = RNS.Identity.recall(destination_hash) server_destination = RNS.Destination( diff --git a/web.py b/web.py index e6c8001..2c75827 100644 --- a/web.py +++ b/web.py @@ -304,12 +304,19 @@ class ReticulumWebChat: # get path params destination_hash = request.match_info.get("destination_hash", "") + timeout_seconds = int(request.query.get("timeout", 15)) + + print(timeout_seconds) # convert destination hash to bytes destination_hash = bytes.fromhex(destination_hash) # initiate audio call - link_hash = await self.audio_call_manager.initiate(destination_hash) + link_hash = await self.audio_call_manager.initiate(destination_hash, timeout_seconds) + if link_hash is None: + return web.json_response({ + "message": "timed out initiating call", + }, status=503) return web.json_response({ "hash": link_hash.hex(),