mirror of
https://github.com/liamcottle/reticulum-meshchat.git
synced 2026-10-25 18:40:12 -04:00
initial implementation of lxst phone calling
This commit is contained in:
parent
011876bec5
commit
99ece9040e
4 changed files with 425 additions and 8 deletions
166
meshchat.py
166
meshchat.py
|
|
@ -1,6 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
|
@ -8,29 +10,28 @@ import platform
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
from datetime import datetime, timezone
|
||||
from typing import Callable, List
|
||||
|
||||
import LXMF
|
||||
import LXST
|
||||
import RNS
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
import LXMF
|
||||
from LXMF import LXMRouter
|
||||
from LXST import Telephone
|
||||
from aiohttp import web, WSMessage, WSMsgType, WSCloseCode
|
||||
import asyncio
|
||||
import base64
|
||||
import webbrowser
|
||||
|
||||
from peewee import SqliteDatabase
|
||||
from serial.tools import list_ports
|
||||
|
||||
import database
|
||||
from src.backend.announce_handler import AnnounceHandler
|
||||
from src.backend.async_utils import AsyncUtils
|
||||
from src.backend.audio_call_manager import AudioCall, AudioCallManager
|
||||
from src.backend.colour_utils import ColourUtils
|
||||
from src.backend.interface_config_parser import InterfaceConfigParser
|
||||
from src.backend.interface_editor import InterfaceEditor
|
||||
from src.backend.lxmf_message_fields import LxmfImageField, LxmfFileAttachmentsField, LxmfFileAttachment, LxmfAudioField
|
||||
from src.backend.audio_call_manager import AudioCall, AudioCallManager
|
||||
from src.backend.sideband_commands import SidebandCommands
|
||||
|
||||
|
||||
|
|
@ -151,6 +152,11 @@ class ReticulumMeshChat:
|
|||
self.audio_call_manager = AudioCallManager(identity=self.identity)
|
||||
self.audio_call_manager.register_incoming_call_callback(self.on_incoming_audio_call)
|
||||
|
||||
# init telephone
|
||||
# todo check if user wants to enable telephone
|
||||
self.telephone = None
|
||||
self.init_telephone()
|
||||
|
||||
# start background thread for auto announce loop
|
||||
thread = threading.Thread(target=asyncio.run, args=(self.announce_loop(),))
|
||||
thread.daemon = True
|
||||
|
|
@ -161,6 +167,34 @@ class ReticulumMeshChat:
|
|||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# init telephone
|
||||
def init_telephone(self):
|
||||
self.telephone = Telephone(identity=self.identity)
|
||||
self.telephone.set_ringing_callback(self.on_telephone_ringing)
|
||||
self.telephone.set_established_callback(self.on_telephone_call_established)
|
||||
self.telephone.set_ended_callback(self.on_telephone_call_ended)
|
||||
|
||||
# handle receiving a new telephone call
|
||||
def on_telephone_ringing(self, caller_identity: RNS.Identity):
|
||||
print("on_telephone_ringing: {}".format(caller_identity.hash.hex()))
|
||||
AsyncUtils.run_async(self.websocket_broadcast(json.dumps({
|
||||
"type": "telephone_ringing",
|
||||
})))
|
||||
|
||||
# handle telephone call established
|
||||
def on_telephone_call_established(self, caller_identity: RNS.Identity):
|
||||
print("on_telephone_call_established: {}".format(caller_identity.hash.hex()))
|
||||
AsyncUtils.run_async(self.websocket_broadcast(json.dumps({
|
||||
"type": "telephone_call_established",
|
||||
})))
|
||||
|
||||
# handle telephone call ended
|
||||
def on_telephone_call_ended(self, caller_identity: RNS.Identity):
|
||||
print("on_telephone_ended: {}".format(caller_identity.hash.hex()))
|
||||
AsyncUtils.run_async(self.websocket_broadcast(json.dumps({
|
||||
"type": "telephone_call_ended",
|
||||
})))
|
||||
|
||||
# gets app version from package.json
|
||||
def get_app_version(self) -> str:
|
||||
with open(get_file_path("package.json")) as f:
|
||||
|
|
@ -309,6 +343,122 @@ class ReticulumMeshChat:
|
|||
"status": "ok",
|
||||
})
|
||||
|
||||
# serve telephone status
|
||||
@routes.get("/api/v1/telephone/status")
|
||||
async def index(request):
|
||||
|
||||
# make sure telephone is enabled
|
||||
if self.telephone is None:
|
||||
return web.json_response({
|
||||
"message": "Telephone is disabled",
|
||||
}, status=503)
|
||||
|
||||
# get active call info
|
||||
active_call = None
|
||||
telephone_active_call = self.telephone.active_call
|
||||
if telephone_active_call is not None:
|
||||
|
||||
# get remote identity hash
|
||||
remote_identity_hash = None
|
||||
remote_identity = telephone_active_call.get_remote_identity()
|
||||
if remote_identity is not None:
|
||||
remote_identity_hash = remote_identity.hash.hex()
|
||||
|
||||
active_call = {
|
||||
"hash": telephone_active_call.hash.hex(),
|
||||
"status": self.telephone.call_status,
|
||||
"is_incoming": telephone_active_call.is_incoming,
|
||||
"is_outgoing": telephone_active_call.is_outgoing,
|
||||
"remote_identity_hash": remote_identity_hash,
|
||||
}
|
||||
|
||||
return web.json_response({
|
||||
"is_busy": self.telephone.busy,
|
||||
"call_status": self.telephone.call_status,
|
||||
"active_call": active_call,
|
||||
})
|
||||
|
||||
# answer incoming telephone call
|
||||
@routes.get("/api/v1/telephone/answer")
|
||||
async def index(request):
|
||||
|
||||
# get incoming caller identity
|
||||
caller_identity = self.telephone.active_call.get_remote_identity()
|
||||
|
||||
# answer call
|
||||
AsyncUtils.run_async(asyncio.to_thread(self.telephone.answer, caller_identity))
|
||||
|
||||
return web.json_response({
|
||||
"message": "Answering call...",
|
||||
})
|
||||
|
||||
# hangup active telephone call
|
||||
@routes.get("/api/v1/telephone/hangup")
|
||||
async def index(request):
|
||||
AsyncUtils.run_async(asyncio.to_thread(self.telephone.hangup))
|
||||
return web.json_response({
|
||||
"message": "Hanging up call...",
|
||||
})
|
||||
|
||||
# initiate a telephone call
|
||||
@routes.get("/api/v1/telephone/call/{identity_hash}")
|
||||
async def index(request):
|
||||
|
||||
# get path params
|
||||
identity_hash_hex = request.match_info.get("identity_hash", "")
|
||||
timeout_seconds = int(request.query.get("timeout", 15))
|
||||
|
||||
# convert hash to bytes
|
||||
identity_hash = bytes.fromhex(identity_hash_hex)
|
||||
|
||||
# find destination identity
|
||||
destination_identity = RNS.Identity.recall(identity_hash, from_identity_hash=True)
|
||||
if destination_identity is None:
|
||||
# couldn't find as identity hash, try finding as destination hash
|
||||
destination_identity = RNS.Identity.recall(identity_hash, from_identity_hash=False)
|
||||
|
||||
# ensure identity was found
|
||||
if destination_identity is None:
|
||||
return web.json_response({
|
||||
"message": "Call Failed: Destination identity not found.",
|
||||
}, status=503)
|
||||
|
||||
# create telephony destination, only used to determine if we have a path
|
||||
telephony_destination = RNS.Destination(
|
||||
destination_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
LXST.APP_NAME,
|
||||
LXST.Primitives.Telephony.PRIMITIVE_NAME
|
||||
)
|
||||
telephony_destination_hash = telephony_destination.hash
|
||||
|
||||
# determine when to timeout
|
||||
timeout_after_seconds = time.time() + timeout_seconds
|
||||
|
||||
# check if we have a path to the destination
|
||||
if not RNS.Transport.has_path(telephony_destination_hash):
|
||||
|
||||
# we don't have a path, so we need to request it
|
||||
RNS.Transport.request_path(telephony_destination_hash)
|
||||
|
||||
# wait until we have a path, or give up after the configured timeout
|
||||
while not RNS.Transport.has_path(telephony_destination_hash) and time.time() < timeout_after_seconds:
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# if we still don't have a path, call will fail, so bail out
|
||||
if not RNS.Transport.has_path(telephony_destination_hash):
|
||||
return web.json_response({
|
||||
"message": "Call Failed: Could not find path to destination.",
|
||||
}, status=503)
|
||||
|
||||
# initiate call
|
||||
AsyncUtils.run_async(asyncio.to_thread(self.telephone.call, destination_identity, None))
|
||||
|
||||
return web.json_response({
|
||||
"message": "Calling...",
|
||||
})
|
||||
|
||||
# fetch com ports
|
||||
@routes.get("/api/v1/comports")
|
||||
async def index(request):
|
||||
|
|
@ -2075,6 +2225,10 @@ class ReticulumMeshChat:
|
|||
# send announce for audio call
|
||||
self.audio_call_manager.announce(app_data=self.config.display_name.get().encode("utf-8"))
|
||||
|
||||
# send announce for telephone if enabled
|
||||
if self.telephone is not None:
|
||||
self.telephone.announce()
|
||||
|
||||
# tell websocket clients we just announced
|
||||
await self.send_announced_to_websocket_clients()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
aiohttp>=3.12.14
|
||||
cx_freeze>=7.0.0
|
||||
lxmf>=0.9.2
|
||||
lxst>=0.4.0
|
||||
peewee>=3.18.1
|
||||
rns>=1.0.2
|
||||
websockets>=14.2
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import axios from 'axios';
|
||||
import {createApp} from 'vue';
|
||||
import "./style.css";
|
||||
import CallPage from "./components/call/CallPage.vue";
|
||||
import TelephonePage from "./components/telephone/TelephonePage.vue";
|
||||
|
||||
// provide axios globally
|
||||
window.axios = axios;
|
||||
|
||||
createApp(CallPage)
|
||||
createApp(TelephonePage)
|
||||
.mount('#app');
|
||||
|
|
|
|||
262
src/frontend/components/telephone/TelephonePage.vue
Normal file
262
src/frontend/components/telephone/TelephonePage.vue
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
<template>
|
||||
<div class="flex w-full h-full bg-gray-100 dark:bg-zinc-950" :class="{'dark': config?.theme === 'dark'}">
|
||||
<div class="mx-auto my-auto w-full max-w-xl p-4">
|
||||
|
||||
<div class="w-full space-y-2">
|
||||
|
||||
<!-- dialer -->
|
||||
<div class="border rounded-xl bg-white shadow w-full overflow-hidden dark:border-zinc-900">
|
||||
<div class="flex border-b border-gray-300 text-gray-700 p-2 dark:bg-zinc-800 dark:text-white">
|
||||
<div class="my-auto mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 dark:text-white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 0 1-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="my-auto">Start a new Call</div>
|
||||
</div>
|
||||
<div class="flex border-b border-gray-300 text-gray-900 p-2 space-x-2 dark:bg-zinc-700 dark:text-zinc-100 dark:border-zinc-800">
|
||||
<div class="flex-1">
|
||||
<input v-model="destinationHash" type="text" placeholder="Enter Destination Hash" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2 dark:bg-zinc-800 dark:border-zinc-700 dark:text-zinc-100">
|
||||
</div>
|
||||
<button @click="initiateCall(destinationHash)" :disabled="isInitiatingCall" type="button" :class="[ isInitiatingCall ? 'bg-gray-400 focus-visible:outline-gray-500' : 'bg-green-500 hover:bg-green-400 focus-visible:outline-green-500' ]" class="my-auto inline-flex items-center gap-x-1 rounded-md p-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
|
||||
<span v-if="isInitiatingCall">
|
||||
<span>Calling...</span>
|
||||
</span>
|
||||
<span v-else>Initiate Call</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex p-1 dark:bg-zinc-700 dark:border-zinc-600">
|
||||
<div>
|
||||
<div class='dark:text-white'>My Identity Hash</div>
|
||||
<div class="text-sm text-gray-700 dark:text-zinc-100">{{ myIdentityHash || "Unknown" }}</div>
|
||||
</div>
|
||||
<div class="ml-auto my-auto mr-1">
|
||||
<a @click="announce" href="javascript:void(0)" class="rounded-full">
|
||||
<div class="flex text-gray-700 bg-gray-100 hover:bg-gray-200 px-2 py-1 rounded-full">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.288 15.038a5.25 5.25 0 0 1 7.424 0M5.106 11.856c3.807-3.808 9.98-3.808 13.788 0M1.924 8.674c5.565-5.565 14.587-5.565 20.152 0M12.53 18.22l-.53.53-.53-.53a.75.75 0 0 1 1.06 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="my-auto mx-1 text-sm">Announce</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- active call -->
|
||||
<div v-if="activeCall" class="border rounded-xl bg-white shadow w-full overflow-hidden dark:bg-zinc-800 dark:border-zinc-700 dark:text-zinc-100">
|
||||
<div class="flex border-b border-gray-300 text-gray-700 p-2 dark:text-zinc-100">
|
||||
<div class="my-auto mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="my-auto">
|
||||
<span v-if="activeCall.is_incoming">Incoming Call</span>
|
||||
<span v-if="activeCall.is_outgoing">Outgoing Call</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y">
|
||||
<div class="flex p-2">
|
||||
<div class="mr-2 my-auto">
|
||||
<div class="bg-gray-100 p-2 rounded-full">
|
||||
<svg v-if="activeCall.is_outgoing" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<path d="M3.5 2A1.5 1.5 0 0 0 2 3.5V5c0 1.149.15 2.263.43 3.326a13.022 13.022 0 0 0 9.244 9.244c1.063.28 2.177.43 3.326.43h1.5a1.5 1.5 0 0 0 1.5-1.5v-1.148a1.5 1.5 0 0 0-1.175-1.465l-3.223-.716a1.5 1.5 0 0 0-1.767 1.052l-.267.933c-.117.41-.555.643-.95.48a11.542 11.542 0 0 1-6.254-6.254c-.163-.395.07-.833.48-.95l.933-.267a1.5 1.5 0 0 0 1.052-1.767l-.716-3.223A1.5 1.5 0 0 0 4.648 2H3.5ZM16.5 4.56l-3.22 3.22a.75.75 0 1 1-1.06-1.06l3.22-3.22h-2.69a.75.75 0 0 1 0-1.5h4.5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0V4.56Z" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<path d="M3.5 2A1.5 1.5 0 0 0 2 3.5V5c0 1.149.15 2.263.43 3.326a13.022 13.022 0 0 0 9.244 9.244c1.063.28 2.177.43 3.326.43h1.5a1.5 1.5 0 0 0 1.5-1.5v-1.148a1.5 1.5 0 0 0-1.175-1.465l-3.223-.716a1.5 1.5 0 0 0-1.767 1.052l-.267.933c-.117.41-.555.643-.95.48a11.542 11.542 0 0 1-6.254-6.254c-.163-.395.07-.833.48-.95l.933-.267a1.5 1.5 0 0 0 1.052-1.767l-.716-3.223A1.5 1.5 0 0 0 4.648 2H3.5ZM16.72 2.22a.75.75 0 1 1 1.06 1.06L14.56 6.5h2.69a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 1 1.5 0v2.69l3.22-3.22Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ activeCall.remote_identity_hash || "Unknown" }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-zinc-100">
|
||||
<span>
|
||||
<span v-if="activeCall.status === 0">Busy...</span>
|
||||
<span v-else-if="activeCall.status === 1">Rejected...</span>
|
||||
<span v-else-if="activeCall.status === 2">Calling...</span>
|
||||
<span v-else-if="activeCall.status === 3">Available...</span>
|
||||
<span v-else-if="activeCall.status === 4">Ringing...</span>
|
||||
<span v-else-if="activeCall.status === 5">Connecting...</span>
|
||||
<span v-else-if="activeCall.status === 6">Established...</span>
|
||||
<span v-else>Status: {{ activeCall.status }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2 ml-auto my-auto mx-2">
|
||||
|
||||
<!-- answer call -->
|
||||
<button v-if="activeCall.is_incoming && activeCall.status === 4" title="Answer Call" @click="answerCall" type="button" class="my-auto inline-flex items-center gap-x-1 rounded-full bg-green-500 p-2 text-sm font-semibold text-white shadow-sm hover:bg-green-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd" d="M2 3.5A1.5 1.5 0 0 1 3.5 2h1.148a1.5 1.5 0 0 1 1.465 1.175l.716 3.223a1.5 1.5 0 0 1-1.052 1.767l-.933.267c-.41.117-.643.555-.48.95a11.542 11.542 0 0 0 6.254 6.254c.395.163.833-.07.95-.48l.267-.933a1.5 1.5 0 0 1 1.767-1.052l3.223.716A1.5 1.5 0 0 1 18 15.352V16.5a1.5 1.5 0 0 1-1.5 1.5H15c-1.149 0-2.263-.15-3.326-.43A13.022 13.022 0 0 1 2.43 8.326 13.019 13.019 0 0 1 2 5V3.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- hangup call -->
|
||||
<button title="Hangup Call" @click="hangupCall" type="button" class="my-auto inline-flex items-center gap-x-1 rounded-full bg-red-500 p-2 text-sm font-semibold text-white shadow-sm hover:bg-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 rotate-[135deg] translate-y-0.5">
|
||||
<path fill-rule="evenodd" d="M2 3.5A1.5 1.5 0 0 1 3.5 2h1.148a1.5 1.5 0 0 1 1.465 1.175l.716 3.223a1.5 1.5 0 0 1-1.052 1.767l-.933.267c-.41.117-.643.555-.48.95a11.542 11.542 0 0 0 6.254 6.254c.395.163.833-.07.95-.48l.267-.933a1.5 1.5 0 0 1 1.767-1.052l3.223.716A1.5 1.5 0 0 1 18 15.352V16.5a1.5 1.5 0 0 1-1.5 1.5H15c-1.149 0-2.263-.15-3.326-.43A13.022 13.022 0 0 1 2.43 8.326 13.019 13.019 0 0 1 2 5V3.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TelephonePage',
|
||||
data() {
|
||||
return {
|
||||
|
||||
config: null,
|
||||
myIdentityHash: null,
|
||||
activeCall: null,
|
||||
|
||||
destinationHash: null,
|
||||
isInitiatingCall: false,
|
||||
|
||||
};
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
// update config
|
||||
this.getConfig();
|
||||
this.getTelephoneStatus();
|
||||
|
||||
// update telephone status every second
|
||||
setInterval(() => {
|
||||
this.getTelephoneStatus();
|
||||
}, 1000);
|
||||
|
||||
// parse url params
|
||||
var queryParams = new URLSearchParams(location.search);
|
||||
var queryDestinationHash = queryParams.get("destination_hash");
|
||||
|
||||
// autofill destination hash to call when query param provided in url
|
||||
if(queryDestinationHash){
|
||||
this.destinationHash = queryDestinationHash;
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
async initiateCall(destinationHash) {
|
||||
|
||||
// do nothing if already initiating call
|
||||
if(this.isInitiatingCall){
|
||||
alert("Call is already initiating...");
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure call hash provided
|
||||
if(!destinationHash) {
|
||||
alert("Enter destination hash to call.");
|
||||
return;
|
||||
}
|
||||
|
||||
// show loading
|
||||
this.isInitiatingCall = true;
|
||||
|
||||
try {
|
||||
|
||||
// initiate call
|
||||
await axios.get(`/api/v1/telephone/call/${destinationHash}`, {
|
||||
params: {
|
||||
timeout: 15, // how long to attempt to initiate call
|
||||
},
|
||||
});
|
||||
|
||||
// fetch status
|
||||
await this.getTelephoneStatus();
|
||||
|
||||
} catch(e) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
},
|
||||
async answerCall() {
|
||||
try {
|
||||
|
||||
// answer call
|
||||
await axios.get(`/api/v1/telephone/answer`);
|
||||
|
||||
// update ui
|
||||
await this.getTelephoneStatus();
|
||||
|
||||
} catch(e) {
|
||||
// ignore error answering call
|
||||
}
|
||||
},
|
||||
async hangupCall() {
|
||||
|
||||
try {
|
||||
|
||||
// hangup call
|
||||
await axios.get(`/api/v1/telephone/hangup`);
|
||||
|
||||
// update ui
|
||||
await this.getTelephoneStatus();
|
||||
|
||||
} catch(e) {
|
||||
// ignore error hanging up call
|
||||
}
|
||||
|
||||
},
|
||||
async getConfig() {
|
||||
try {
|
||||
|
||||
// fetch calls
|
||||
const response = await axios.get("/api/v1/config");
|
||||
|
||||
// update ui
|
||||
this.config = response.data.config;
|
||||
this.myIdentityHash = response.data.config.identity_hash;
|
||||
|
||||
} catch(e) {
|
||||
// do nothing on error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
async getTelephoneStatus() {
|
||||
try {
|
||||
|
||||
// fetch calls
|
||||
const response = await axios.get("/api/v1/telephone/status");
|
||||
|
||||
// update ui
|
||||
this.activeCall = response.data.active_call;
|
||||
|
||||
} catch(e) {
|
||||
// do nothing on error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
async announce() {
|
||||
try {
|
||||
await window.axios.get(`/api/v1/announce`);
|
||||
} catch(e) {
|
||||
alert("failed to announce");
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue