api(jsonrpc): add state attribute to call info

Existing boolean attributes removed.
This commit is contained in:
link2xt
2025-09-25 22:38:50 +00:00
committed by l
parent a5c82425f4
commit 1ba448fe19
2 changed files with 69 additions and 31 deletions

View File

@@ -1,5 +1,6 @@
use anyhow::Result;
use deltachat::calls::{call_state, CallState};
use deltachat::context::Context;
use deltachat::message::MsgId;
use serde::Serialize;
@@ -8,46 +9,79 @@ use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "CallInfo", rename_all = "camelCase")]
pub struct JsonrpcCallInfo {
/// True if the call is an incoming call.
pub is_incoming: bool,
/// True if the call should not ring anymore.
pub is_stale: bool,
/// True if the call is accepted.
pub is_accepted: bool,
/// True if the call has been ended.
pub is_ended: bool,
/// Call duration in seconds.
pub duration: i64,
/// SDP offer.
///
/// Can be used to manually answer the call
/// even if incoming call event was missed.
pub sdp_offer: String,
/// Call state.
///
/// For example, if the call is accepted, active, cancelled, declined etc.
pub state: JsonrpcCallState,
}
impl JsonrpcCallInfo {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<JsonrpcCallInfo> {
let call_info = context.load_call_by_id(msg_id).await?;
let is_incoming = call_info.is_incoming();
let is_stale = call_info.is_stale();
let is_accepted = call_info.is_accepted();
let is_ended = call_info.is_ended();
let duration = call_info.duration_seconds();
let sdp_offer = call_info.place_call_info.clone();
let state = JsonrpcCallState::from_msg_id(context, msg_id).await?;
Ok(JsonrpcCallInfo {
is_incoming,
is_stale,
is_accepted,
is_ended,
duration,
sdp_offer,
})
Ok(JsonrpcCallInfo { sdp_offer, state })
}
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "CallState", tag = "kind")]
pub enum JsonrpcCallState {
/// Fresh incoming or outgoing call that is still ringing.
///
/// There is no separate state for outgoing call
/// that has been dialled but not ringing on the other side yet
/// as we don't know whether the other side received our call.
Alerting,
/// Active call.
Active,
/// Completed call that was once active
/// and then was terminated for any reason.
Completed {
/// Call duration in seconds.
duration: i64,
},
/// Incoming call that was not picked up within a timeout
/// or was explicitly ended by the caller before we picked up.
Missed,
/// Incoming call that was explicitly ended on our side
/// before picking up or outgoing call
/// that was declined before the timeout.
Declined,
/// Outgoing call that has been cancelled on our side
/// before receiving a response.
///
/// Incoming calls cannot be cancelled,
/// on the receiver side cancelled calls
/// usually result in missed calls.
Cancelled,
}
impl JsonrpcCallState {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<JsonrpcCallState> {
let call_state = call_state(context, msg_id).await?;
let jsonrpc_call_state = match call_state {
CallState::Alerting => JsonrpcCallState::Alerting,
CallState::Active => JsonrpcCallState::Active,
CallState::Completed { duration } => JsonrpcCallState::Completed { duration },
CallState::Missed => JsonrpcCallState::Missed,
CallState::Declined => JsonrpcCallState::Declined,
CallState::Cancelled => JsonrpcCallState::Cancelled,
};
Ok(jsonrpc_call_state)
}
}

View File

@@ -10,23 +10,27 @@ def test_calls(acfactory) -> None:
alice_contact_bob = alice.create_contact(bob, "Bob")
alice_chat_bob = alice_contact_bob.create_chat()
outgoing_call_message = alice_chat_bob.place_outgoing_call(place_call_info)
assert outgoing_call_message.get_call_info().state.kind == "Alerting"
incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL)
assert incoming_call_event.place_call_info == place_call_info
assert not incoming_call_event.has_video # Cannot be parsed as SDP, so false by default
incoming_call_message = Message(bob, incoming_call_event.msg_id)
assert not incoming_call_message.get_call_info().is_accepted
assert incoming_call_message.get_call_info().state.kind == "Alerting"
incoming_call_message.accept_incoming_call(accept_call_info)
assert incoming_call_message.get_call_info().is_accepted
assert incoming_call_message.get_call_info().sdp_offer == place_call_info
assert incoming_call_message.get_call_info().state.kind == "Active"
outgoing_call_accepted_event = alice.wait_for_event(EventType.OUTGOING_CALL_ACCEPTED)
assert outgoing_call_accepted_event.accept_call_info == accept_call_info
assert outgoing_call_message.get_call_info().state.kind == "Active"
outgoing_call_message.end_call()
assert outgoing_call_message.get_call_info().state.kind == "Completed"
end_call_event = bob.wait_for_event(EventType.CALL_ENDED)
assert end_call_event.msg_id == outgoing_call_message.id
assert incoming_call_message.get_call_info().state.kind == "Completed"
def test_video_call(acfactory) -> None: