Skip to content

Commit af4c33a

Browse files
committed
feat: Synchronize encrypted groups creation across devices (#7001)
Unencrypted groups don't have grpid since key-contacts were merged, so we don't sync them for now.
1 parent 8fc6ea1 commit af4c33a

File tree

4 files changed

+63
-15
lines changed

4 files changed

+63
-15
lines changed

deltachat-ffi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1676,7 +1676,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
16761676
};
16771677

16781678
block_on(async move {
1679-
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
1679+
chat::create_group(ctx, Some(protect), &to_string_lossy(name))
16801680
.await
16811681
.context("Failed to create group chat")
16821682
.log_err(ctx)

deltachat-jsonrpc/src/api.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ impl CommandApi {
977977
true => ProtectionStatus::Protected,
978978
false => ProtectionStatus::Unprotected,
979979
};
980-
chat::create_group_ex(&ctx, Some(protect), &name)
980+
chat::create_group(&ctx, Some(protect), &name)
981981
.await
982982
.map(|id| id.to_u32())
983983
}
@@ -988,7 +988,7 @@ impl CommandApi {
988988
/// address-contacts.
989989
async fn create_group_chat_unencrypted(&self, account_id: u32, name: String) -> Result<u32> {
990990
let ctx = self.get_context(account_id).await?;
991-
chat::create_group_ex(&ctx, None, &name)
991+
chat::create_group(&ctx, None, &name)
992992
.await
993993
.map(|id| id.to_u32())
994994
}

src/chat.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3657,30 +3657,45 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul
36573657
}
36583658

36593659
/// Creates a group chat with a given `name`.
3660-
/// Deprecated on 2025-06-21, use `create_group_ex()`.
3660+
/// Deprecated on 2025-06-21, use `create_group()`.
36613661
pub async fn create_group_chat(
36623662
context: &Context,
36633663
protect: ProtectionStatus,
36643664
name: &str,
36653665
) -> Result<ChatId> {
3666-
create_group_ex(context, Some(protect), name).await
3666+
create_group(context, Some(protect), name).await
36673667
}
36683668

36693669
/// Creates a group chat.
36703670
///
36713671
/// * `encryption` - If `Some`, the chat is encrypted (with key-contacts) and can be protected.
36723672
/// * `name` - Chat name.
3673-
pub async fn create_group_ex(
3673+
pub async fn create_group(
36743674
context: &Context,
36753675
encryption: Option<ProtectionStatus>,
36763676
name: &str,
3677+
) -> Result<ChatId> {
3678+
create_group_ex(context, Sync, encryption.map(|p| (create_id(), p)), name).await
3679+
}
3680+
3681+
/// Creates a group chat.
3682+
///
3683+
/// * `sync` - Whether a multi-device synchronization message should be sent. Ignored for
3684+
/// unencrypted chats currently.
3685+
///
3686+
/// See [`create_group`] for other parameters.
3687+
pub(crate) async fn create_group_ex(
3688+
context: &Context,
3689+
sync: sync::Sync,
3690+
encryption: Option<(String /* grpid */, ProtectionStatus)>,
3691+
name: &str,
36773692
) -> Result<ChatId> {
36783693
let chat_name = sanitize_single_line(name);
36793694
ensure!(!chat_name.is_empty(), "Invalid chat name");
36803695

3681-
let grpid = match encryption {
3682-
Some(_) => create_id(),
3683-
None => String::new(),
3696+
let (grpid, protection) = match encryption {
3697+
Some((grpid, protection)) => (grpid, Some(protection)),
3698+
None => (String::new(), None),
36843699
};
36853700

36863701
let timestamp = create_smeared_timestamp(context);
@@ -3690,7 +3705,7 @@ pub async fn create_group_ex(
36903705
"INSERT INTO chats
36913706
(type, name, grpid, param, created_timestamp)
36923707
VALUES(?, ?, ?, \'U=1\', ?);",
3693-
(Chattype::Group, chat_name, grpid, timestamp),
3708+
(Chattype::Group, &chat_name, &grpid, timestamp),
36943709
)
36953710
.await?;
36963711

@@ -3701,7 +3716,7 @@ pub async fn create_group_ex(
37013716
chatlist_events::emit_chatlist_changed(context);
37023717
chatlist_events::emit_chatlist_item_changed(context, chat_id);
37033718

3704-
if encryption == Some(ProtectionStatus::Protected) {
3719+
if protection == Some(ProtectionStatus::Protected) {
37053720
let protect = ProtectionStatus::Protected;
37063721
chat_id
37073722
.set_protection_for_timestamp_sort(context, protect, timestamp, None)
@@ -3714,7 +3729,11 @@ pub async fn create_group_ex(
37143729
let text = stock_str::new_group_send_first_message(context).await;
37153730
add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
37163731
}
3717-
3732+
if let (true, Some(protection)) = (sync.into(), protection) {
3733+
let id = SyncId::Grpid(grpid);
3734+
let action = SyncAction::CreateGroupEncrypted(protection, chat_name);
3735+
self::sync(context, id, action).await.log_err(context).ok();
3736+
}
37183737
Ok(chat_id)
37193738
}
37203739

@@ -4985,7 +5004,7 @@ pub(crate) enum SyncId {
49855004
/// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
49865005
Msgids(Vec<String>),
49875006

4988-
// Special id for device chat.
5007+
/// Special id for device chat.
49895008
Device,
49905009
}
49915010

@@ -4999,6 +5018,8 @@ pub(crate) enum SyncAction {
49995018
SetMuted(MuteDuration),
50005019
/// Create broadcast channel with the given name.
50015020
CreateBroadcast(String),
5021+
/// Create encrypted group chat with the given name.
5022+
CreateGroupEncrypted(ProtectionStatus, String),
50025023
Rename(String),
50035024
/// Set chat contacts by their addresses.
50045025
SetContacts(Vec<String>),
@@ -5064,6 +5085,9 @@ impl Context {
50645085
if let SyncAction::CreateBroadcast(name) = action {
50655086
create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
50665087
return Ok(());
5088+
} else if let SyncAction::CreateGroupEncrypted(protection, name) = action {
5089+
create_group_ex(self, Nosync, Some((grpid.clone(), *protection)), name).await?;
5090+
return Ok(());
50675091
}
50685092
get_chat_id_by_grpid(self, grpid)
50695093
.await?
@@ -5085,7 +5109,7 @@ impl Context {
50855109
SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
50865110
SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
50875111
SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
5088-
SyncAction::CreateBroadcast(_) => {
5112+
SyncAction::CreateBroadcast(_) | SyncAction::CreateGroupEncrypted(..) => {
50895113
Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
50905114
}
50915115
SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,

src/chat/chat_tests.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,30 @@ async fn test_sync_name() -> Result<()> {
38003800
Ok(())
38013801
}
38023802

3803+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3804+
async fn test_sync_create_group() -> Result<()> {
3805+
let mut tcm = TestContextManager::new();
3806+
let alice0 = &tcm.alice().await;
3807+
let alice1 = &tcm.alice().await;
3808+
for a in [alice0, alice1] {
3809+
a.set_config_bool(Config::SyncMsgs, true).await?;
3810+
}
3811+
for protection in [ProtectionStatus::Unprotected, ProtectionStatus::Protected] {
3812+
let a0_chat_id = create_group(alice0, Some(protection), "grp").await?;
3813+
sync(alice0, alice1).await;
3814+
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
3815+
let a1_chat_id = get_chat_id_by_grpid(alice1, &a0_chat.grpid)
3816+
.await?
3817+
.unwrap()
3818+
.0;
3819+
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
3820+
assert_eq!(a1_chat.get_type(), Chattype::Group);
3821+
assert_eq!(a1_chat.protected, protection);
3822+
assert_eq!(a1_chat.get_name(), "grp");
3823+
}
3824+
Ok(())
3825+
}
3826+
38033827
/// Tests sending JPEG image with .png extension.
38043828
///
38053829
/// This is a regression test, previously sending failed
@@ -4707,7 +4731,7 @@ async fn test_create_unencrypted_group_chat() -> Result<()> {
47074731
let bob = &tcm.bob().await;
47084732
let charlie = &tcm.charlie().await;
47094733

4710-
let chat_id = create_group_ex(alice, None, "Group chat").await?;
4734+
let chat_id = create_group(alice, None, "Group chat").await?;
47114735
let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await;
47124736
let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await;
47134737

0 commit comments

Comments
 (0)