Skip to content

Commit 2530a6c

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 e3973f6 commit 2530a6c

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
@@ -3699,30 +3699,45 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul
36993699
}
37003700

37013701
/// Creates a group chat with a given `name`.
3702-
/// Deprecated on 2025-06-21, use `create_group_ex()`.
3702+
/// Deprecated on 2025-06-21, use `create_group()`.
37033703
pub async fn create_group_chat(
37043704
context: &Context,
37053705
protect: ProtectionStatus,
37063706
name: &str,
37073707
) -> Result<ChatId> {
3708-
create_group_ex(context, Some(protect), name).await
3708+
create_group(context, Some(protect), name).await
37093709
}
37103710

37113711
/// Creates a group chat.
37123712
///
37133713
/// * `encryption` - If `Some`, the chat is encrypted (with key-contacts) and can be protected.
37143714
/// * `name` - Chat name.
3715-
pub async fn create_group_ex(
3715+
pub async fn create_group(
37163716
context: &Context,
37173717
encryption: Option<ProtectionStatus>,
37183718
name: &str,
3719+
) -> Result<ChatId> {
3720+
create_group_ex(context, Sync, encryption.map(|p| (create_id(), p)), name).await
3721+
}
3722+
3723+
/// Creates a group chat.
3724+
///
3725+
/// * `sync` - Whether a multi-device synchronization message should be sent. Ignored for
3726+
/// unencrypted chats currently.
3727+
///
3728+
/// See [`create_group`] for other parameters.
3729+
pub(crate) async fn create_group_ex(
3730+
context: &Context,
3731+
sync: sync::Sync,
3732+
encryption: Option<(String /* grpid */, ProtectionStatus)>,
3733+
name: &str,
37193734
) -> Result<ChatId> {
37203735
let chat_name = sanitize_single_line(name);
37213736
ensure!(!chat_name.is_empty(), "Invalid chat name");
37223737

3723-
let grpid = match encryption {
3724-
Some(_) => create_id(),
3725-
None => String::new(),
3738+
let (grpid, protection) = match encryption {
3739+
Some((grpid, protection)) => (grpid, Some(protection)),
3740+
None => (String::new(), None),
37263741
};
37273742

37283743
let timestamp = create_smeared_timestamp(context);
@@ -3732,7 +3747,7 @@ pub async fn create_group_ex(
37323747
"INSERT INTO chats
37333748
(type, name, grpid, param, created_timestamp)
37343749
VALUES(?, ?, ?, \'U=1\', ?);",
3735-
(Chattype::Group, chat_name, grpid, timestamp),
3750+
(Chattype::Group, &chat_name, &grpid, timestamp),
37363751
)
37373752
.await?;
37383753

@@ -3743,7 +3758,7 @@ pub async fn create_group_ex(
37433758
chatlist_events::emit_chatlist_changed(context);
37443759
chatlist_events::emit_chatlist_item_changed(context, chat_id);
37453760

3746-
if encryption == Some(ProtectionStatus::Protected) {
3761+
if protection == Some(ProtectionStatus::Protected) {
37473762
let protect = ProtectionStatus::Protected;
37483763
chat_id
37493764
.set_protection_for_timestamp_sort(context, protect, timestamp, None)
@@ -3756,7 +3771,11 @@ pub async fn create_group_ex(
37563771
let text = stock_str::new_group_send_first_message(context).await;
37573772
add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
37583773
}
3759-
3774+
if let (true, Some(protection)) = (sync.into(), protection) {
3775+
let id = SyncId::Grpid(grpid);
3776+
let action = SyncAction::CreateGroupEncrypted(protection, chat_name);
3777+
self::sync(context, id, action).await.log_err(context).ok();
3778+
}
37603779
Ok(chat_id)
37613780
}
37623781

@@ -5027,7 +5046,7 @@ pub(crate) enum SyncId {
50275046
/// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
50285047
Msgids(Vec<String>),
50295048

5030-
// Special id for device chat.
5049+
/// Special id for device chat.
50315050
Device,
50325051
}
50335052

@@ -5041,6 +5060,8 @@ pub(crate) enum SyncAction {
50415060
SetMuted(MuteDuration),
50425061
/// Create broadcast channel with the given name.
50435062
CreateBroadcast(String),
5063+
/// Create encrypted group chat with the given name.
5064+
CreateGroupEncrypted(ProtectionStatus, String),
50445065
Rename(String),
50455066
/// Set chat contacts by their addresses.
50465067
SetContacts(Vec<String>),
@@ -5106,6 +5127,9 @@ impl Context {
51065127
if let SyncAction::CreateBroadcast(name) = action {
51075128
create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
51085129
return Ok(());
5130+
} else if let SyncAction::CreateGroupEncrypted(protection, name) = action {
5131+
create_group_ex(self, Nosync, Some((grpid.clone(), *protection)), name).await?;
5132+
return Ok(());
51095133
}
51105134
get_chat_id_by_grpid(self, grpid)
51115135
.await?
@@ -5127,7 +5151,7 @@ impl Context {
51275151
SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
51285152
SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
51295153
SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
5130-
SyncAction::CreateBroadcast(_) => {
5154+
SyncAction::CreateBroadcast(_) | SyncAction::CreateGroupEncrypted(..) => {
51315155
Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
51325156
}
51335157
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
@@ -3812,6 +3812,30 @@ async fn test_sync_name() -> Result<()> {
38123812
Ok(())
38133813
}
38143814

3815+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3816+
async fn test_sync_create_group() -> Result<()> {
3817+
let mut tcm = TestContextManager::new();
3818+
let alice0 = &tcm.alice().await;
3819+
let alice1 = &tcm.alice().await;
3820+
for a in [alice0, alice1] {
3821+
a.set_config_bool(Config::SyncMsgs, true).await?;
3822+
}
3823+
for protection in [ProtectionStatus::Unprotected, ProtectionStatus::Protected] {
3824+
let a0_chat_id = create_group(alice0, Some(protection), "grp").await?;
3825+
sync(alice0, alice1).await;
3826+
let a0_chat = Chat::load_from_db(alice0, a0_chat_id).await?;
3827+
let a1_chat_id = get_chat_id_by_grpid(alice1, &a0_chat.grpid)
3828+
.await?
3829+
.unwrap()
3830+
.0;
3831+
let a1_chat = Chat::load_from_db(alice1, a1_chat_id).await?;
3832+
assert_eq!(a1_chat.get_type(), Chattype::Group);
3833+
assert_eq!(a1_chat.protected, protection);
3834+
assert_eq!(a1_chat.get_name(), "grp");
3835+
}
3836+
Ok(())
3837+
}
3838+
38153839
/// Tests sending JPEG image with .png extension.
38163840
///
38173841
/// This is a regression test, previously sending failed
@@ -4725,7 +4749,7 @@ async fn test_create_unencrypted_group_chat() -> Result<()> {
47254749
let bob = &tcm.bob().await;
47264750
let charlie = &tcm.charlie().await;
47274751

4728-
let chat_id = create_group_ex(alice, None, "Group chat").await?;
4752+
let chat_id = create_group(alice, None, "Group chat").await?;
47294753
let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await;
47304754
let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await;
47314755

0 commit comments

Comments
 (0)