1#[cfg(feature = "group-e2ee")]
2use crate::internal::auth::session::{AsyncSessionProvider, SessionProvider};
3#[cfg(feature = "group-e2ee")]
4use crate::internal::group_e2ee::provider::GroupMlsProvider;
5#[cfg(feature = "group-e2ee")]
6use crate::internal::transport::{AsyncAuthenticatedRpcTransport, AuthenticatedRpcTransport};
7
8pub struct GroupService<'a> {
9 client: &'a crate::core::ImClient,
10}
11
12impl<'a> GroupService<'a> {
13 pub(crate) fn new(client: &'a crate::core::ImClient) -> Self {
14 Self { client }
15 }
16
17 pub fn publish_key_package(
18 &self,
19 request: super::GroupKeyPackagePublishRequest,
20 ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
21 #[cfg(feature = "group-e2ee")]
22 {
23 self.publish_key_package_with_group_e2ee(request)
24 }
25 #[cfg(not(feature = "group-e2ee"))]
26 {
27 let _ = request;
28 Err(crate::ImError::unsupported("group-e2ee"))
29 }
30 }
31
32 pub async fn publish_key_package_async(
33 &self,
34 request: super::GroupKeyPackagePublishRequest,
35 ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
36 #[cfg(feature = "group-e2ee")]
37 {
38 self.publish_key_package_with_group_e2ee_async(request)
39 .await
40 }
41 #[cfg(not(feature = "group-e2ee"))]
42 {
43 let _ = request;
44 Err(crate::ImError::unsupported("group-e2ee"))
45 }
46 }
47
48 pub fn create(
49 &self,
50 request: super::GroupCreateRequest,
51 ) -> crate::ImResult<super::GroupReadResult> {
52 let secure_required = group_create_uses_e2ee(&request);
53 #[cfg(not(feature = "group-e2ee"))]
54 if secure_required {
55 return Err(crate::ImError::unsupported("group-e2ee"));
56 }
57 #[cfg(feature = "group-e2ee")]
58 let secure_provider = if secure_required {
59 ensure_group_e2ee_service_available(self.client, false)?;
60 Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
61 } else {
62 None
63 };
64 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
65 self.client,
66 crate::internal::auth::session::FileSessionProvider::new(self.client),
67 crate::internal::transport::CoreHttpTransport::new(self.client),
68 )
69 .create(request, None)?;
70 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
71 self.refresh_group_state(&mut result, true);
72 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
73 #[cfg(feature = "group-e2ee")]
74 if secure_required {
75 let group =
76 group_did(&result).ok_or_else(|| crate::ImError::LocalStateUnavailable {
77 detail: "group E2EE create requires created group DID".to_owned(),
78 })?;
79 let group_state_ref = group_state_ref_from_result(&group, &result);
80 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
81 self.client,
82 crate::internal::auth::session::FileSessionProvider::new(self.client),
83 crate::internal::transport::CoreHttpTransport::new(self.client),
84 secure_provider.expect("secure provider initialized when secure_required"),
85 )
86 .create_secure_group(
87 crate::internal::group_e2ee::lifecycle::GroupE2eeCreateInput {
88 group: crate::ids::GroupRef::parse(&group)?,
89 credentials: None,
90 service_did: None,
91 group_state_ref,
92 },
93 )?;
94 result.warnings.extend(secure.warnings);
95 }
96 Ok(result)
97 }
98
99 pub async fn create_async(
100 &self,
101 request: super::GroupCreateRequest,
102 ) -> crate::ImResult<super::GroupReadResult> {
103 let secure_required = group_create_uses_e2ee(&request);
104 #[cfg(not(feature = "group-e2ee"))]
105 if secure_required {
106 return Err(crate::ImError::unsupported("group-e2ee"));
107 }
108 #[cfg(feature = "group-e2ee")]
109 if secure_required {
110 ensure_group_e2ee_service_available_async(self.client, false).await?;
111 }
112 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
113 self.client,
114 crate::internal::auth::session::FileSessionProvider::new(self.client),
115 crate::internal::transport::CoreHttpTransport::new(self.client),
116 )
117 .create_async(request, None)
118 .await?;
119 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
120 self.client,
121 &result,
122 )
123 .await;
124 self.refresh_group_state_async(&mut result, true).await;
125 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
126 self.client,
127 &result,
128 )
129 .await;
130 #[cfg(feature = "group-e2ee")]
131 if secure_required {
132 let group =
133 group_did(&result).ok_or_else(|| crate::ImError::LocalStateUnavailable {
134 detail: "group E2EE create requires created group DID".to_owned(),
135 })?;
136 let group_state_ref = group_state_ref_from_result(&group, &result);
137 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
138 self.client,
139 crate::internal::auth::session::FileSessionProvider::new(self.client),
140 crate::internal::transport::CoreHttpTransport::new(self.client),
141 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
142 )
143 .create_secure_group_async(
144 crate::internal::group_e2ee::lifecycle::GroupE2eeCreateInput {
145 group: crate::ids::GroupRef::parse(&group)?,
146 credentials: None,
147 service_did: None,
148 group_state_ref,
149 },
150 )
151 .await?;
152 result.warnings.extend(secure.warnings);
153 }
154 Ok(result)
155 }
156
157 pub fn join(
158 &self,
159 request: super::GroupJoinRequest,
160 ) -> crate::ImResult<super::GroupReadResult> {
161 let group = request.group.as_str().to_string();
162 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
163 self.client,
164 crate::internal::auth::session::FileSessionProvider::new(self.client),
165 crate::internal::transport::CoreHttpTransport::new(self.client),
166 )
167 .join(request, None)?;
168 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
169 self.refresh_group_state_for(&mut result, &group, true);
170 Ok(result)
171 }
172
173 pub async fn join_async(
174 &self,
175 request: super::GroupJoinRequest,
176 ) -> crate::ImResult<super::GroupReadResult> {
177 let group = request.group.as_str().to_string();
178 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
179 self.client,
180 crate::internal::auth::session::FileSessionProvider::new(self.client),
181 crate::internal::transport::CoreHttpTransport::new(self.client),
182 )
183 .join_async(request, None)
184 .await?;
185 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
186 self.client,
187 &result,
188 )
189 .await;
190 self.refresh_group_state_for_async(&mut result, &group, true)
191 .await;
192 Ok(result)
193 }
194
195 pub fn leave(
196 &self,
197 request: super::GroupLeaveRequest,
198 ) -> crate::ImResult<super::GroupReadResult> {
199 let group = request.group.as_str().to_string();
200 #[cfg(not(feature = "group-e2ee"))]
201 if request.security.required() {
202 return Err(crate::ImError::unsupported("group-e2ee"));
203 }
204 #[cfg(feature = "group-e2ee")]
205 let secure_provider = if request.security.required() {
206 ensure_group_e2ee_service_available(self.client, false)?;
207 Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
208 } else {
209 None
210 };
211 if let Ok(Some(snapshot)) =
212 crate::internal::group_runtime::cache::cached_group_snapshot(self.client, &group)
213 {
214 if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
215 return Err(crate::ImError::invalid_input(
216 Some("group".to_string()),
217 "group owner cannot leave the group",
218 ));
219 }
220 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
221 && !request.security.required()
222 {
223 return Err(crate::ImError::unsupported("group-e2ee"));
224 }
225 }
226 if request.security.required() {
227 #[cfg(feature = "group-e2ee")]
228 {
229 let secure =
230 crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
231 self.client,
232 crate::internal::auth::session::FileSessionProvider::new(self.client),
233 crate::internal::transport::CoreHttpTransport::new(self.client),
234 secure_provider.expect("secure provider initialized when secure_required"),
235 )
236 .leave_secure_group(
237 crate::internal::group_e2ee::lifecycle::GroupE2eeLeaveInput {
238 group: request.group,
239 reason_text: request.reason_text,
240 owner_leave_commit: false,
241 credentials: None,
242 },
243 )?;
244 return Ok(super::GroupReadResult::from_raw_response(
245 secure.delivery,
246 secure.warnings,
247 ));
248 }
249 }
250 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
251 self.client,
252 crate::internal::auth::session::FileSessionProvider::new(self.client),
253 crate::internal::transport::CoreHttpTransport::new(self.client),
254 )
255 .leave(request, None)?;
256 crate::internal::group_runtime::projection::project_group_left(self.client, &group);
257 Ok(result)
258 }
259
260 pub async fn leave_async(
261 &self,
262 request: super::GroupLeaveRequest,
263 ) -> crate::ImResult<super::GroupReadResult> {
264 let group = request.group.as_str().to_string();
265 #[cfg(not(feature = "group-e2ee"))]
266 if request.security.required() {
267 return Err(crate::ImError::unsupported("group-e2ee"));
268 }
269 #[cfg(feature = "group-e2ee")]
270 if request.security.required() {
271 ensure_group_e2ee_service_available_async(self.client, false).await?;
272 if let Ok(Some(snapshot)) =
273 crate::internal::group_runtime::cache::cached_group_snapshot_async(
274 self.client,
275 &group,
276 )
277 .await
278 {
279 if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
280 return Err(crate::ImError::invalid_input(
281 Some("group".to_string()),
282 "group owner cannot leave the group",
283 ));
284 }
285 }
286 let session_provider =
287 crate::internal::auth::session::FileSessionProvider::new(self.client);
288 let mut transport = crate::internal::transport::CoreHttpTransport::new(self.client);
289 let secure = crate::internal::group_e2ee::lifecycle::leave_secure_group_request_async(
290 self.client,
291 &session_provider,
292 &mut transport,
293 crate::internal::group_e2ee::lifecycle::GroupE2eeLeaveInput {
294 group: request.group,
295 reason_text: request.reason_text,
296 owner_leave_commit: false,
297 credentials: None,
298 },
299 )
300 .await?;
301 return Ok(super::GroupReadResult::from_raw_response(
302 secure.delivery,
303 secure.warnings,
304 ));
305 }
306 if let Ok(Some(snapshot)) =
307 crate::internal::group_runtime::cache::cached_group_snapshot_async(self.client, &group)
308 .await
309 {
310 if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
311 return Err(crate::ImError::invalid_input(
312 Some("group".to_string()),
313 "group owner cannot leave the group",
314 ));
315 }
316 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
317 && !request.security.required()
318 {
319 return Err(crate::ImError::unsupported("group-e2ee"));
320 }
321 }
322 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
323 self.client,
324 crate::internal::auth::session::FileSessionProvider::new(self.client),
325 crate::internal::transport::CoreHttpTransport::new(self.client),
326 )
327 .leave_async(request, None)
328 .await?;
329 let _ = crate::internal::group_runtime::projection::project_group_left_async(
330 self.client,
331 &group,
332 )
333 .await;
334 Ok(result)
335 }
336
337 pub fn add_member(
338 &self,
339 request: super::GroupMemberMutationRequest,
340 ) -> crate::ImResult<super::GroupReadResult> {
341 let group = request.group.as_str().to_string();
342 let resolved_member = resolve_group_member(self.client, &request.member)?;
343 #[cfg(feature = "group-e2ee")]
344 let member = resolved_member.did.as_str().to_string();
345 #[cfg(feature = "group-e2ee")]
346 let reason_text = request.reason_text.clone();
347 #[cfg(not(feature = "group-e2ee"))]
348 if request.security.required() {
349 return Err(crate::ImError::unsupported("group-e2ee"));
350 }
351 #[cfg(feature = "group-e2ee")]
352 let secure_provider = if request.security.required() {
353 ensure_group_e2ee_service_available(self.client, true)?;
354 Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
355 } else {
356 None
357 };
358 if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
359 self.client,
360 request.group.as_str(),
361 ) {
362 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
363 && !request.security.required()
364 {
365 return Err(crate::ImError::unsupported("group-e2ee"));
366 }
367 }
368 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
369 self.client,
370 crate::internal::auth::session::FileSessionProvider::new(self.client),
371 crate::internal::transport::CoreHttpTransport::new(self.client),
372 )
373 .add_member(
374 resolved_group_member_request(request, &resolved_member),
375 None,
376 )?;
377 result.resolved_member = Some(resolved_member.clone());
378 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
379 project_group_system_event_best_effort(self.client, &group, &mut result);
380 self.refresh_group_state_for(&mut result, &group, true);
381 #[cfg(feature = "group-e2ee")]
382 if let Some(secure_provider) = secure_provider {
383 let group_state_ref = group_state_ref_from_result(&group, &result);
384 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
385 self.client,
386 crate::internal::auth::session::FileSessionProvider::new(self.client),
387 crate::internal::transport::CoreHttpTransport::new(self.client),
388 secure_provider,
389 )
390 .add_secure_member(
391 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
392 group: crate::ids::GroupRef::parse(&group)?,
393 member: crate::ids::Did::parse(&member)?,
394 reason_text,
395 leave_request_id: None,
396 credentials: None,
397 service_did: None,
398 group_state_ref,
399 },
400 )?;
401 result = super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
402 result.resolved_member = Some(resolved_member.clone());
403 project_group_system_event_best_effort(self.client, &group, &mut result);
404 self.refresh_group_state_for(&mut result, &group, true);
405 }
406 Ok(result)
407 }
408
409 pub async fn add_member_async(
410 &self,
411 request: super::GroupMemberMutationRequest,
412 ) -> crate::ImResult<super::GroupReadResult> {
413 let secure_required = request.security.required();
414 let group = request.group.as_str().to_string();
415 let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
416 #[cfg(feature = "group-e2ee")]
417 let member = resolved_member.did.as_str().to_string();
418 #[cfg(feature = "group-e2ee")]
419 let reason_text = request.reason_text.clone();
420 #[cfg(not(feature = "group-e2ee"))]
421 if secure_required {
422 return Err(crate::ImError::unsupported("group-e2ee"));
423 }
424 #[cfg(feature = "group-e2ee")]
425 if secure_required {
426 ensure_group_e2ee_service_available_async(self.client, true).await?;
427 }
428 if let Ok(Some(snapshot)) =
429 crate::internal::group_runtime::cache::cached_group_snapshot_async(
430 self.client,
431 request.group.as_str(),
432 )
433 .await
434 {
435 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
436 && !secure_required
437 {
438 return Err(crate::ImError::unsupported("group-e2ee"));
439 }
440 }
441 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
442 self.client,
443 crate::internal::auth::session::FileSessionProvider::new(self.client),
444 crate::internal::transport::CoreHttpTransport::new(self.client),
445 )
446 .add_member_async(
447 resolved_group_member_request(request, &resolved_member),
448 None,
449 )
450 .await?;
451 result.resolved_member = Some(resolved_member.clone());
452 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
453 self.client,
454 &result,
455 )
456 .await;
457 project_group_system_event_best_effort_async(self.client, &group, &mut result).await;
458 self.refresh_group_state_for_async(&mut result, &group, true)
459 .await;
460 #[cfg(feature = "group-e2ee")]
461 if secure_required {
462 let group_state_ref = group_state_ref_from_result(&group, &result);
463 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
464 self.client,
465 crate::internal::auth::session::FileSessionProvider::new(self.client),
466 crate::internal::transport::CoreHttpTransport::new(self.client),
467 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
468 )
469 .add_secure_member_async(
470 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
471 group: crate::ids::GroupRef::parse(&group)?,
472 member: crate::ids::Did::parse(&member)?,
473 reason_text,
474 leave_request_id: None,
475 credentials: None,
476 service_did: None,
477 group_state_ref,
478 },
479 )
480 .await?;
481 result = super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
482 result.resolved_member = Some(resolved_member.clone());
483 project_group_system_event_best_effort_async(self.client, &group, &mut result).await;
484 self.refresh_group_state_for_async(&mut result, &group, true)
485 .await;
486 }
487 Ok(result)
488 }
489
490 pub fn remove_member(
491 &self,
492 request: super::GroupMemberMutationRequest,
493 ) -> crate::ImResult<super::GroupReadResult> {
494 let group = request.group.as_str().to_string();
495 let resolved_member = resolve_group_member(self.client, &request.member)?;
496 #[cfg(not(feature = "group-e2ee"))]
497 if request.security.required() {
498 return Err(crate::ImError::unsupported("group-e2ee"));
499 }
500 #[cfg(feature = "group-e2ee")]
501 let secure_provider = if request.security.required() {
502 ensure_group_e2ee_service_available(self.client, false)?;
503 Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
504 } else {
505 None
506 };
507 if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
508 self.client,
509 request.group.as_str(),
510 ) {
511 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
512 && !request.security.required()
513 {
514 return Err(crate::ImError::unsupported("group-e2ee"));
515 }
516 }
517 if request.security.required() {
518 #[cfg(feature = "group-e2ee")]
519 {
520 let group_state_ref = group_state_ref_from_current_group(self, &group);
521 let secure =
522 crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
523 self.client,
524 crate::internal::auth::session::FileSessionProvider::new(self.client),
525 crate::internal::transport::CoreHttpTransport::new(self.client),
526 secure_provider.expect("secure provider initialized when secure_required"),
527 )
528 .remove_secure_member(
529 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
530 group: request.group,
531 member: resolved_member.did.clone(),
532 reason_text: request.reason_text,
533 leave_request_id: request.leave_request_id,
534 credentials: None,
535 service_did: None,
536 group_state_ref,
537 },
538 )?;
539 let mut result =
540 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
541 result.resolved_member = Some(resolved_member);
542 self.refresh_group_state_for(&mut result, &group, true);
543 return Ok(result);
544 }
545 }
546 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
547 self.client,
548 crate::internal::auth::session::FileSessionProvider::new(self.client),
549 crate::internal::transport::CoreHttpTransport::new(self.client),
550 )
551 .remove_member(
552 resolved_group_member_request(request, &resolved_member),
553 None,
554 )?;
555 result.resolved_member = Some(resolved_member.clone());
556 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
557 self.refresh_group_state_for(&mut result, &group, true);
558 Ok(result)
559 }
560
561 pub async fn remove_member_async(
562 &self,
563 request: super::GroupMemberMutationRequest,
564 ) -> crate::ImResult<super::GroupReadResult> {
565 let secure_required = request.security.required();
566 let group = request.group.as_str().to_string();
567 let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
568 #[cfg(not(feature = "group-e2ee"))]
569 if secure_required {
570 return Err(crate::ImError::unsupported("group-e2ee"));
571 }
572 #[cfg(feature = "group-e2ee")]
573 if secure_required {
574 ensure_group_e2ee_service_available_async(self.client, false).await?;
575 }
576 if let Ok(Some(snapshot)) =
577 crate::internal::group_runtime::cache::cached_group_snapshot_async(
578 self.client,
579 request.group.as_str(),
580 )
581 .await
582 {
583 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
584 && !secure_required
585 {
586 return Err(crate::ImError::unsupported("group-e2ee"));
587 }
588 }
589 #[cfg(feature = "group-e2ee")]
590 if secure_required {
591 let group_state_ref = group_state_ref_from_current_group_async(self, &group).await;
592 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
593 self.client,
594 crate::internal::auth::session::FileSessionProvider::new(self.client),
595 crate::internal::transport::CoreHttpTransport::new(self.client),
596 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
597 )
598 .remove_secure_member_async(
599 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
600 group: request.group,
601 member: resolved_member.did.clone(),
602 reason_text: request.reason_text,
603 leave_request_id: request.leave_request_id,
604 credentials: None,
605 service_did: None,
606 group_state_ref,
607 },
608 )
609 .await?;
610 let mut result =
611 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
612 result.resolved_member = Some(resolved_member);
613 self.refresh_group_state_for_async(&mut result, &group, true)
614 .await;
615 return Ok(result);
616 }
617 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
618 self.client,
619 crate::internal::auth::session::FileSessionProvider::new(self.client),
620 crate::internal::transport::CoreHttpTransport::new(self.client),
621 )
622 .remove_member_async(
623 resolved_group_member_request(request, &resolved_member),
624 None,
625 )
626 .await?;
627 result.resolved_member = Some(resolved_member.clone());
628 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
629 self.client,
630 &result,
631 )
632 .await;
633 self.refresh_group_state_for_async(&mut result, &group, true)
634 .await;
635 Ok(result)
636 }
637
638 pub fn process_e2ee_leave_request(
639 &self,
640 request: super::GroupE2eeProcessLeaveRequest,
641 ) -> crate::ImResult<super::GroupReadResult> {
642 #[cfg(not(feature = "group-e2ee"))]
643 {
644 let _ = request;
645 return Err(crate::ImError::unsupported("group-e2ee"));
646 }
647 #[cfg(feature = "group-e2ee")]
648 {
649 ensure_group_e2ee_service_available(self.client, false)?;
650 let group = request.group.as_str().to_owned();
651 let resolved_member = resolve_group_member(self.client, &request.member)?;
652 let group_state_ref = group_state_ref_from_current_group(self, &group);
653 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
654 self.client,
655 crate::internal::auth::session::FileSessionProvider::new(self.client),
656 crate::internal::transport::CoreHttpTransport::new(self.client),
657 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
658 )
659 .process_leave_request(
660 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
661 group: request.group,
662 member: resolved_member.did.clone(),
663 reason_text: request.reason_text,
664 leave_request_id: Some(request.leave_request_id),
665 credentials: None,
666 service_did: None,
667 group_state_ref,
668 },
669 )?;
670 let mut result =
671 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
672 result.resolved_member = Some(resolved_member);
673 self.refresh_group_state_for(&mut result, &group, true);
674 Ok(result)
675 }
676 }
677
678 pub async fn process_e2ee_leave_request_async(
679 &self,
680 request: super::GroupE2eeProcessLeaveRequest,
681 ) -> crate::ImResult<super::GroupReadResult> {
682 #[cfg(not(feature = "group-e2ee"))]
683 {
684 let _ = request;
685 return Err(crate::ImError::unsupported("group-e2ee"));
686 }
687 #[cfg(feature = "group-e2ee")]
688 {
689 ensure_group_e2ee_service_available_async(self.client, false).await?;
690 let group = request.group.as_str().to_owned();
691 let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
692 let group_state_ref = group_state_ref_from_current_group_async(self, &group).await;
693 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
694 self.client,
695 crate::internal::auth::session::FileSessionProvider::new(self.client),
696 crate::internal::transport::CoreHttpTransport::new(self.client),
697 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
698 )
699 .process_leave_request_async(
700 crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
701 group: request.group,
702 member: resolved_member.did.clone(),
703 reason_text: request.reason_text,
704 leave_request_id: Some(request.leave_request_id),
705 credentials: None,
706 service_did: None,
707 group_state_ref,
708 },
709 )
710 .await?;
711 let mut result =
712 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
713 result.resolved_member = Some(resolved_member);
714 self.refresh_group_state_for_async(&mut result, &group, true)
715 .await;
716 Ok(result)
717 }
718 }
719
720 pub fn update_member_key(
721 &self,
722 request: super::GroupE2eeUpdateKeyRequest,
723 ) -> crate::ImResult<super::GroupReadResult> {
724 #[cfg(not(feature = "group-e2ee"))]
725 {
726 let _ = request;
727 return Err(crate::ImError::unsupported("group-e2ee"));
728 }
729 #[cfg(feature = "group-e2ee")]
730 {
731 ensure_group_e2ee_service_available(self.client, true)?;
732 let group = request.group.as_str().to_owned();
733 let resolved_member = resolve_group_member(self.client, &request.member)?;
734 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
735 self.client,
736 crate::internal::auth::session::FileSessionProvider::new(self.client),
737 crate::internal::transport::CoreHttpTransport::new(self.client),
738 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
739 )
740 .update_member_key(
741 crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
742 group: request.group,
743 member: resolved_member.did.clone(),
744 device_id: request.device_id.unwrap_or_default(),
745 credentials: None,
746 service_did: None,
747 },
748 )?;
749 let mut result =
750 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
751 result.resolved_member = Some(resolved_member);
752 self.refresh_group_state_for(&mut result, &group, true);
753 Ok(result)
754 }
755 }
756
757 pub async fn update_member_key_async(
758 &self,
759 request: super::GroupE2eeUpdateKeyRequest,
760 ) -> crate::ImResult<super::GroupReadResult> {
761 #[cfg(not(feature = "group-e2ee"))]
762 {
763 let _ = request;
764 return Err(crate::ImError::unsupported("group-e2ee"));
765 }
766 #[cfg(feature = "group-e2ee")]
767 {
768 ensure_group_e2ee_service_available_async(self.client, true).await?;
769 let group = request.group.as_str().to_owned();
770 let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
771 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
772 self.client,
773 crate::internal::auth::session::FileSessionProvider::new(self.client),
774 crate::internal::transport::CoreHttpTransport::new(self.client),
775 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
776 )
777 .update_member_key_async(
778 crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
779 group: request.group,
780 member: resolved_member.did.clone(),
781 device_id: request.device_id.unwrap_or_default(),
782 credentials: None,
783 service_did: None,
784 },
785 )
786 .await?;
787 let mut result =
788 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
789 result.resolved_member = Some(resolved_member);
790 self.refresh_group_state_for_async(&mut result, &group, true)
791 .await;
792 Ok(result)
793 }
794 }
795
796 pub fn recover_member(
797 &self,
798 request: super::GroupE2eeRecoverMemberRequest,
799 ) -> crate::ImResult<super::GroupReadResult> {
800 #[cfg(not(feature = "group-e2ee"))]
801 {
802 let _ = request;
803 return Err(crate::ImError::unsupported("group-e2ee"));
804 }
805 #[cfg(feature = "group-e2ee")]
806 {
807 ensure_group_e2ee_service_available(self.client, true)?;
808 let group = request.group.as_str().to_owned();
809 let resolved_member = resolve_group_member(self.client, &request.member)?;
810 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
811 self.client,
812 crate::internal::auth::session::FileSessionProvider::new(self.client),
813 crate::internal::transport::CoreHttpTransport::new(self.client),
814 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
815 )
816 .recover_member(
817 crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
818 group: request.group,
819 member: resolved_member.did.clone(),
820 device_id: request.device_id.unwrap_or_default(),
821 credentials: None,
822 service_did: None,
823 },
824 )?;
825 let mut result =
826 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
827 result.resolved_member = Some(resolved_member);
828 self.refresh_group_state_for(&mut result, &group, true);
829 Ok(result)
830 }
831 }
832
833 pub async fn recover_member_async(
834 &self,
835 request: super::GroupE2eeRecoverMemberRequest,
836 ) -> crate::ImResult<super::GroupReadResult> {
837 #[cfg(not(feature = "group-e2ee"))]
838 {
839 let _ = request;
840 return Err(crate::ImError::unsupported("group-e2ee"));
841 }
842 #[cfg(feature = "group-e2ee")]
843 {
844 ensure_group_e2ee_service_available_async(self.client, true).await?;
845 let group = request.group.as_str().to_owned();
846 let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
847 let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
848 self.client,
849 crate::internal::auth::session::FileSessionProvider::new(self.client),
850 crate::internal::transport::CoreHttpTransport::new(self.client),
851 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
852 )
853 .recover_member_async(
854 crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
855 group: request.group,
856 member: resolved_member.did.clone(),
857 device_id: request.device_id.unwrap_or_default(),
858 credentials: None,
859 service_did: None,
860 },
861 )
862 .await?;
863 let mut result =
864 super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
865 result.resolved_member = Some(resolved_member);
866 self.refresh_group_state_for_async(&mut result, &group, true)
867 .await;
868 Ok(result)
869 }
870 }
871
872 pub fn update_profile(
873 &self,
874 request: super::GroupUpdateProfileRequest,
875 ) -> crate::ImResult<super::GroupReadResult> {
876 let group = request.group.as_str().to_string();
877 if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
878 self.client,
879 request.group.as_str(),
880 ) {
881 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
882 return Err(crate::ImError::unsupported("group-e2ee"));
883 }
884 }
885 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
886 self.client,
887 crate::internal::auth::session::FileSessionProvider::new(self.client),
888 crate::internal::transport::CoreHttpTransport::new(self.client),
889 )
890 .update_profile(request, None)?;
891 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
892 self.refresh_group_state_for(&mut result, &group, false);
893 Ok(result)
894 }
895
896 pub async fn update_profile_async(
897 &self,
898 request: super::GroupUpdateProfileRequest,
899 ) -> crate::ImResult<super::GroupReadResult> {
900 let group = request.group.as_str().to_string();
901 if let Ok(Some(snapshot)) =
902 crate::internal::group_runtime::cache::cached_group_snapshot_async(
903 self.client,
904 request.group.as_str(),
905 )
906 .await
907 {
908 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
909 return Err(crate::ImError::unsupported("group-e2ee"));
910 }
911 }
912 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
913 self.client,
914 crate::internal::auth::session::FileSessionProvider::new(self.client),
915 crate::internal::transport::CoreHttpTransport::new(self.client),
916 )
917 .update_profile_async(request, None)
918 .await?;
919 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
920 self.client,
921 &result,
922 )
923 .await;
924 self.refresh_group_state_for_async(&mut result, &group, false)
925 .await;
926 Ok(result)
927 }
928
929 pub fn update_policy(
930 &self,
931 request: super::GroupUpdatePolicyRequest,
932 ) -> crate::ImResult<super::GroupReadResult> {
933 let group = request.group.as_str().to_string();
934 if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
935 self.client,
936 request.group.as_str(),
937 ) {
938 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
939 return Err(crate::ImError::unsupported("group-e2ee"));
940 }
941 }
942 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
943 self.client,
944 crate::internal::auth::session::FileSessionProvider::new(self.client),
945 crate::internal::transport::CoreHttpTransport::new(self.client),
946 )
947 .update_policy(request, None)?;
948 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
949 self.refresh_group_state_for(&mut result, &group, false);
950 Ok(result)
951 }
952
953 pub async fn update_policy_async(
954 &self,
955 request: super::GroupUpdatePolicyRequest,
956 ) -> crate::ImResult<super::GroupReadResult> {
957 let group = request.group.as_str().to_string();
958 if let Ok(Some(snapshot)) =
959 crate::internal::group_runtime::cache::cached_group_snapshot_async(
960 self.client,
961 request.group.as_str(),
962 )
963 .await
964 {
965 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
966 return Err(crate::ImError::unsupported("group-e2ee"));
967 }
968 }
969 let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
970 self.client,
971 crate::internal::auth::session::FileSessionProvider::new(self.client),
972 crate::internal::transport::CoreHttpTransport::new(self.client),
973 )
974 .update_policy_async(request, None)
975 .await?;
976 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
977 self.client,
978 &result,
979 )
980 .await;
981 self.refresh_group_state_for_async(&mut result, &group, false)
982 .await;
983 Ok(result)
984 }
985
986 pub fn update(
987 &self,
988 request: super::GroupUpdateRequest,
989 ) -> crate::ImResult<super::GroupUpdateResult> {
990 if request.profile_patch == super::GroupProfilePatch::default()
991 && request.policy_patch == super::GroupPolicyPatch::default()
992 {
993 return Err(crate::ImError::invalid_input(
994 Some("group_update".to_string()),
995 "group update requires at least one mutable field",
996 ));
997 }
998 if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
999 self.client,
1000 request.group.as_str(),
1001 ) {
1002 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
1003 return Err(crate::ImError::unsupported("group-e2ee"));
1004 }
1005 }
1006 let group = request.group;
1007 let mut deliveries = Vec::new();
1008 let mut warnings = Vec::new();
1009 if request.profile_patch != super::GroupProfilePatch::default() {
1010 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1011 self.client,
1012 crate::internal::auth::session::FileSessionProvider::new(self.client),
1013 crate::internal::transport::CoreHttpTransport::new(self.client),
1014 )
1015 .update_profile(
1016 super::GroupUpdateProfileRequest {
1017 group: group.clone(),
1018 patch: request.profile_patch,
1019 },
1020 None,
1021 )?;
1022 crate::internal::group_runtime::projection::project_group_snapshot(
1023 self.client,
1024 &result,
1025 );
1026 warnings.extend(result.warnings.iter().cloned());
1027 deliveries.push(result);
1028 }
1029 if request.policy_patch != super::GroupPolicyPatch::default() {
1030 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1031 self.client,
1032 crate::internal::auth::session::FileSessionProvider::new(self.client),
1033 crate::internal::transport::CoreHttpTransport::new(self.client),
1034 )
1035 .update_policy(
1036 super::GroupUpdatePolicyRequest {
1037 group: group.clone(),
1038 patch: request.policy_patch,
1039 },
1040 None,
1041 )?;
1042 crate::internal::group_runtime::projection::project_group_snapshot(
1043 self.client,
1044 &result,
1045 );
1046 warnings.extend(result.warnings.iter().cloned());
1047 deliveries.push(result);
1048 }
1049 let mut refreshed = match self.get(group.clone()) {
1050 Ok(result) => Some(result),
1051 Err(err) => {
1052 warnings.push(format!("Failed to refresh group snapshot: {err}"));
1053 None
1054 }
1055 };
1056 if let Some(ref mut refreshed) = refreshed {
1057 warnings.extend(refreshed.warnings.iter().cloned());
1058 }
1059 Ok(super::GroupUpdateResult {
1060 deliveries,
1061 refreshed,
1062 warnings,
1063 })
1064 }
1065
1066 pub async fn update_async(
1067 &self,
1068 request: super::GroupUpdateRequest,
1069 ) -> crate::ImResult<super::GroupUpdateResult> {
1070 if request.profile_patch == super::GroupProfilePatch::default()
1071 && request.policy_patch == super::GroupPolicyPatch::default()
1072 {
1073 return Err(crate::ImError::invalid_input(
1074 Some("group_update".to_string()),
1075 "group update requires at least one mutable field",
1076 ));
1077 }
1078 if let Ok(Some(snapshot)) =
1079 crate::internal::group_runtime::cache::cached_group_snapshot_async(
1080 self.client,
1081 request.group.as_str(),
1082 )
1083 .await
1084 {
1085 if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
1086 return Err(crate::ImError::unsupported("group-e2ee"));
1087 }
1088 }
1089 let group = request.group;
1090 let mut deliveries = Vec::new();
1091 let mut warnings = Vec::new();
1092 if request.profile_patch != super::GroupProfilePatch::default() {
1093 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1094 self.client,
1095 crate::internal::auth::session::FileSessionProvider::new(self.client),
1096 crate::internal::transport::CoreHttpTransport::new(self.client),
1097 )
1098 .update_profile_async(
1099 super::GroupUpdateProfileRequest {
1100 group: group.clone(),
1101 patch: request.profile_patch,
1102 },
1103 None,
1104 )
1105 .await?;
1106 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1107 self.client,
1108 &result,
1109 )
1110 .await;
1111 warnings.extend(result.warnings.iter().cloned());
1112 deliveries.push(result);
1113 }
1114 if request.policy_patch != super::GroupPolicyPatch::default() {
1115 let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1116 self.client,
1117 crate::internal::auth::session::FileSessionProvider::new(self.client),
1118 crate::internal::transport::CoreHttpTransport::new(self.client),
1119 )
1120 .update_policy_async(
1121 super::GroupUpdatePolicyRequest {
1122 group: group.clone(),
1123 patch: request.policy_patch,
1124 },
1125 None,
1126 )
1127 .await?;
1128 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1129 self.client,
1130 &result,
1131 )
1132 .await;
1133 warnings.extend(result.warnings.iter().cloned());
1134 deliveries.push(result);
1135 }
1136 let mut refreshed = match self.get_async(group.clone()).await {
1137 Ok(result) => Some(result),
1138 Err(err) => {
1139 warnings.push(format!("Failed to refresh group snapshot: {err}"));
1140 None
1141 }
1142 };
1143 if let Some(ref mut refreshed) = refreshed {
1144 warnings.extend(refreshed.warnings.iter().cloned());
1145 }
1146 Ok(super::GroupUpdateResult {
1147 deliveries,
1148 refreshed,
1149 warnings,
1150 })
1151 }
1152
1153 pub fn get(&self, group: crate::ids::GroupRef) -> crate::ImResult<super::GroupReadResult> {
1154 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1155 self.client,
1156 crate::internal::auth::session::FileSessionProvider::new(self.client),
1157 crate::internal::transport::CoreHttpTransport::new(self.client),
1158 )
1159 .get(group)?;
1160 crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
1161 Ok(result)
1162 }
1163
1164 pub async fn get_async(
1165 &self,
1166 group: crate::ids::GroupRef,
1167 ) -> crate::ImResult<super::GroupReadResult> {
1168 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1169 self.client,
1170 crate::internal::auth::session::FileSessionProvider::new(self.client),
1171 crate::internal::transport::CoreHttpTransport::new(self.client),
1172 )
1173 .get_async(group)
1174 .await?;
1175 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1176 self.client,
1177 &result,
1178 )
1179 .await;
1180 Ok(result)
1181 }
1182
1183 pub fn list(
1184 &self,
1185 request: super::GroupListRequest,
1186 ) -> crate::ImResult<super::GroupReadResult> {
1187 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1188 self.client,
1189 crate::internal::auth::session::FileSessionProvider::new(self.client),
1190 crate::internal::transport::CoreHttpTransport::new(self.client),
1191 )
1192 .list(request)?;
1193 crate::internal::group_runtime::projection::project_group_summaries(self.client, &result);
1194 Ok(result)
1195 }
1196
1197 pub async fn list_async(
1198 &self,
1199 request: super::GroupListRequest,
1200 ) -> crate::ImResult<super::GroupReadResult> {
1201 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1202 self.client,
1203 crate::internal::auth::session::FileSessionProvider::new(self.client),
1204 crate::internal::transport::CoreHttpTransport::new(self.client),
1205 )
1206 .list_async(request)
1207 .await?;
1208 let _ = crate::internal::group_runtime::projection::project_group_summaries_async(
1209 self.client,
1210 &result,
1211 )
1212 .await;
1213 Ok(result)
1214 }
1215
1216 pub fn members(
1217 &self,
1218 request: super::GroupMembersRequest,
1219 ) -> crate::ImResult<super::GroupReadResult> {
1220 let group = request.group.as_str().to_string();
1221 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1222 self.client,
1223 crate::internal::auth::session::FileSessionProvider::new(self.client),
1224 crate::internal::transport::CoreHttpTransport::new(self.client),
1225 )
1226 .members(request)?;
1227 crate::internal::group_runtime::projection::project_group_members(
1228 self.client,
1229 &group,
1230 &result,
1231 );
1232 Ok(result)
1233 }
1234
1235 pub async fn members_async(
1236 &self,
1237 request: super::GroupMembersRequest,
1238 ) -> crate::ImResult<super::GroupReadResult> {
1239 let group = request.group.as_str().to_string();
1240 let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1241 self.client,
1242 crate::internal::auth::session::FileSessionProvider::new(self.client),
1243 crate::internal::transport::CoreHttpTransport::new(self.client),
1244 )
1245 .members_async(request)
1246 .await?;
1247 let _ = crate::internal::group_runtime::projection::project_group_members_async(
1248 self.client,
1249 &group,
1250 &result,
1251 )
1252 .await;
1253 Ok(result)
1254 }
1255
1256 pub fn messages(
1257 &self,
1258 request: super::GroupMessagesRequest,
1259 ) -> crate::ImResult<super::GroupReadResult> {
1260 let limit = request.limit;
1261 let group = request.group.as_str().to_string();
1262 let mut result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1263 self.client,
1264 crate::internal::auth::session::FileSessionProvider::new(self.client),
1265 crate::internal::transport::CoreHttpTransport::new(self.client),
1266 )
1267 .messages(request)?;
1268 crate::internal::group_runtime::projection::project_group_messages(
1269 self.client,
1270 &group,
1271 &result,
1272 );
1273 let _ = crate::internal::message_runtime::local_projection::persist_messages(
1274 self.client,
1275 &result.messages.items,
1276 );
1277 result.replace_messages(
1278 crate::internal::message_runtime::read::merge_group_local_projection_best_effort(
1279 self.client,
1280 result.messages.clone(),
1281 &crate::ids::GroupRef::parse(&group)?,
1282 limit,
1283 ),
1284 );
1285 Ok(result)
1286 }
1287
1288 pub async fn messages_async(
1289 &self,
1290 request: super::GroupMessagesRequest,
1291 ) -> crate::ImResult<super::GroupReadResult> {
1292 let group = request.group.as_str().to_string();
1293 let limit = request.limit;
1294 let mut result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1295 self.client,
1296 crate::internal::auth::session::FileSessionProvider::new(self.client),
1297 crate::internal::transport::CoreHttpTransport::new(self.client),
1298 )
1299 .messages_async(request)
1300 .await?;
1301 let _ = crate::internal::group_runtime::projection::project_group_messages_async(
1302 self.client,
1303 &group,
1304 &result,
1305 )
1306 .await;
1307 let _ = crate::internal::message_runtime::local_projection::persist_messages_async(
1308 self.client,
1309 &result.messages.items,
1310 )
1311 .await;
1312 let merged =
1313 crate::internal::message_runtime::read::merge_group_local_projection_best_effort_async(
1314 self.client,
1315 result.messages.clone(),
1316 &crate::ids::GroupRef::parse(&group)?,
1317 limit,
1318 )
1319 .await;
1320 result.replace_messages(merged);
1321 Ok(result)
1322 }
1323
1324 fn refresh_group_state(&self, result: &mut super::GroupReadResult, include_members: bool) {
1325 let Some(group) = group_did(result) else {
1326 return;
1327 };
1328 self.refresh_group_state_for(result, &group, include_members);
1329 }
1330
1331 fn refresh_group_state_for(
1332 &self,
1333 result: &mut super::GroupReadResult,
1334 group: &str,
1335 include_members: bool,
1336 ) {
1337 let group_ref = match crate::ids::GroupRef::parse(group) {
1338 Ok(group_ref) => group_ref,
1339 Err(err) => {
1340 result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1341 return;
1342 }
1343 };
1344 match self.get(group_ref.clone()) {
1345 Ok(snapshot) => {
1346 crate::internal::group_runtime::projection::project_group_snapshot(
1347 self.client,
1348 &snapshot,
1349 );
1350 result.merge_group_snapshot_from(&snapshot);
1351 }
1352 Err(err) => {
1353 result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1354 return;
1355 }
1356 }
1357 if !include_members {
1358 return;
1359 }
1360 match self.members(super::GroupMembersRequest {
1361 group: group_ref,
1362 limit: crate::ids::PageLimit(100),
1363 }) {
1364 Ok(members) => result.merge_group_members_from(&members),
1365 Err(err) => result.push_warning(format!("Failed to refresh group members: {err}")),
1366 }
1367 }
1368
1369 async fn refresh_group_state_async(
1370 &self,
1371 result: &mut super::GroupReadResult,
1372 include_members: bool,
1373 ) {
1374 let Some(group) = group_did(result) else {
1375 return;
1376 };
1377 self.refresh_group_state_for_async(result, &group, include_members)
1378 .await;
1379 }
1380
1381 async fn refresh_group_state_for_async(
1382 &self,
1383 result: &mut super::GroupReadResult,
1384 group: &str,
1385 include_members: bool,
1386 ) {
1387 let group_ref = match crate::ids::GroupRef::parse(group) {
1388 Ok(group_ref) => group_ref,
1389 Err(err) => {
1390 result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1391 return;
1392 }
1393 };
1394 match self.get_async(group_ref.clone()).await {
1395 Ok(snapshot) => {
1396 let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1397 self.client,
1398 &snapshot,
1399 )
1400 .await;
1401 result.merge_group_snapshot_from(&snapshot);
1402 }
1403 Err(err) => {
1404 result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1405 return;
1406 }
1407 }
1408 if !include_members {
1409 return;
1410 }
1411 match self
1412 .members_async(super::GroupMembersRequest {
1413 group: group_ref,
1414 limit: crate::ids::PageLimit(100),
1415 })
1416 .await
1417 {
1418 Ok(members) => result.merge_group_members_from(&members),
1419 Err(err) => result.push_warning(format!("Failed to refresh group members: {err}")),
1420 }
1421 }
1422
1423 #[cfg(feature = "group-e2ee")]
1424 fn publish_key_package_with_group_e2ee(
1425 &self,
1426 request: super::GroupKeyPackagePublishRequest,
1427 ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
1428 let session_provider =
1429 crate::internal::auth::session::FileSessionProvider::new(self.client);
1430 <crate::internal::auth::session::FileSessionProvider<'_> as SessionProvider>::ensure_session(
1431 &session_provider,
1432 crate::auth::AuthScope::GroupMessaging,
1433 )?;
1434 let credentials = crate::internal::message_runtime::group::load_credentials(self.client)?;
1435 let service_did = group_e2ee_service_did(self.client)?;
1436 let provider =
1437 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?;
1438 let prepared = prepare_group_key_package_publish(
1439 self.client,
1440 &request,
1441 &credentials,
1442 &service_did,
1443 &provider,
1444 )?;
1445 let mut transport = crate::internal::transport::CoreHttpTransport::new(self.client);
1446 let raw_response =
1447 <crate::internal::transport::CoreHttpTransport<'_> as AuthenticatedRpcTransport>::authenticated_rpc(
1448 &mut transport,
1449 crate::internal::message_runtime::group::MESSAGE_RPC_ENDPOINT,
1450 "group.e2ee.publish_key_package",
1451 prepared.params.clone(),
1452 )?;
1453 Ok(group_key_package_publish_result(
1454 self.client,
1455 request,
1456 prepared,
1457 raw_response,
1458 ))
1459 }
1460
1461 #[cfg(feature = "group-e2ee")]
1462 async fn publish_key_package_with_group_e2ee_async(
1463 &self,
1464 request: super::GroupKeyPackagePublishRequest,
1465 ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
1466 let session_provider =
1467 crate::internal::auth::session::FileSessionProvider::new(self.client);
1468 <crate::internal::auth::session::FileSessionProvider<'_> as AsyncSessionProvider>::ensure_session(
1469 &session_provider,
1470 crate::auth::AuthScope::GroupMessaging,
1471 )
1472 .await?;
1473 let credentials =
1474 crate::internal::message_runtime::group::load_credentials_async(self.client).await?;
1475 let service_did = group_e2ee_service_did(self.client)?;
1476 let provider =
1477 crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?;
1478 let transport = crate::internal::transport::CoreHttpTransport::new(self.client);
1479 publish_group_key_package_with_components(
1480 self.client,
1481 request,
1482 credentials,
1483 service_did,
1484 provider,
1485 transport,
1486 )
1487 .await
1488 }
1489}
1490
1491#[cfg(feature = "group-e2ee")]
1492struct PreparedGroupKeyPackagePublish {
1493 device_id: String,
1494 key_package_id: String,
1495 params: serde_json::Value,
1496}
1497
1498#[cfg(feature = "group-e2ee")]
1499fn prepare_group_key_package_publish<P>(
1500 client: &crate::core::ImClient,
1501 request: &super::GroupKeyPackagePublishRequest,
1502 credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1503 service_did: &crate::ids::Did,
1504 provider: &P,
1505) -> crate::ImResult<PreparedGroupKeyPackagePublish>
1506where
1507 P: GroupMlsProvider + ?Sized,
1508{
1509 let operation_id = format!(
1510 "op-{}",
1511 crate::internal::wire::common::generate_operation_id()
1512 );
1513 let device_id = request
1514 .device_id
1515 .as_deref()
1516 .map(str::trim)
1517 .filter(|value| !value.is_empty())
1518 .unwrap_or(crate::internal::group_e2ee::DEFAULT_GROUP_MLS_DEVICE_ID)
1519 .to_owned();
1520 let mut group_key_package = provider
1521 .generate_key_package(anp::group_e2ee::operations::GenerateKeyPackageInput {
1522 owner_did: client.did().as_str().to_owned(),
1523 device_id: device_id.clone(),
1524 operation_id: operation_id.clone(),
1525 request_id: format!("group-e2ee-key-package-{operation_id}"),
1526 key_package_id: request
1527 .key_package_id
1528 .as_deref()
1529 .map(str::trim)
1530 .filter(|value| !value.is_empty())
1531 .map(str::to_owned),
1532 purpose: Some(request.purpose.as_str().to_owned()),
1533 group_did: request
1534 .group
1535 .as_ref()
1536 .map(|group| group.as_str().to_owned()),
1537 })?
1538 .group_key_package;
1539 group_key_package.did_wba_binding = sign_group_key_package_binding(
1540 credentials,
1541 client.did().as_str(),
1542 group_key_package.did_wba_binding,
1543 )?;
1544 let key_package_id = group_key_package.key_package_id.clone();
1545 let params =
1546 crate::internal::group_e2ee::wire::build_group_e2ee_publish_key_package_rpc_params(
1547 credentials,
1548 client.did().as_str(),
1549 service_did.as_str(),
1550 &group_key_package,
1551 &operation_id,
1552 )?;
1553 Ok(PreparedGroupKeyPackagePublish {
1554 device_id,
1555 key_package_id,
1556 params,
1557 })
1558}
1559
1560#[cfg(feature = "group-e2ee")]
1561fn group_key_package_publish_result(
1562 client: &crate::core::ImClient,
1563 request: super::GroupKeyPackagePublishRequest,
1564 prepared: PreparedGroupKeyPackagePublish,
1565 raw_response: serde_json::Value,
1566) -> super::GroupKeyPackagePublishResult {
1567 super::GroupKeyPackagePublishResult {
1568 owner_did: client.did().clone(),
1569 device_id: prepared.device_id,
1570 key_package_id: prepared.key_package_id,
1571 purpose: request.purpose,
1572 group: request.group,
1573 raw_response,
1574 warnings: Vec::new(),
1575 }
1576}
1577
1578#[cfg(feature = "group-e2ee")]
1579async fn publish_group_key_package_with_components<P, T>(
1580 client: &crate::core::ImClient,
1581 request: super::GroupKeyPackagePublishRequest,
1582 credentials: crate::internal::message_runtime::group::GroupTextCredentials,
1583 service_did: crate::ids::Did,
1584 provider: P,
1585 mut transport: T,
1586) -> crate::ImResult<super::GroupKeyPackagePublishResult>
1587where
1588 P: GroupMlsProvider + Send + 'static,
1589 T: AsyncAuthenticatedRpcTransport,
1590{
1591 let client_did = client.did().as_str().to_owned();
1592 let prepared = prepare_group_key_package_publish_async(
1593 client_did.as_str(),
1594 &request,
1595 &credentials,
1596 &service_did,
1597 provider,
1598 )
1599 .await?;
1600 let raw_response = transport
1601 .authenticated_rpc(
1602 crate::internal::message_runtime::group::MESSAGE_RPC_ENDPOINT,
1603 "group.e2ee.publish_key_package",
1604 prepared.params.clone(),
1605 )
1606 .await?;
1607 Ok(group_key_package_publish_result(
1608 client,
1609 request,
1610 prepared,
1611 raw_response,
1612 ))
1613}
1614
1615#[cfg(feature = "group-e2ee")]
1616async fn prepare_group_key_package_publish_async<P>(
1617 owner_did: &str,
1618 request: &super::GroupKeyPackagePublishRequest,
1619 credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1620 service_did: &crate::ids::Did,
1621 provider: P,
1622) -> crate::ImResult<PreparedGroupKeyPackagePublish>
1623where
1624 P: GroupMlsProvider + Send + 'static,
1625{
1626 let operation_id = format!(
1627 "op-{}",
1628 crate::internal::wire::common::generate_operation_id()
1629 );
1630 let device_id = request
1631 .device_id
1632 .as_deref()
1633 .map(str::trim)
1634 .filter(|value| !value.is_empty())
1635 .unwrap_or(crate::internal::group_e2ee::DEFAULT_GROUP_MLS_DEVICE_ID)
1636 .to_owned();
1637 let input = anp::group_e2ee::operations::GenerateKeyPackageInput {
1638 owner_did: owner_did.to_owned(),
1639 device_id: device_id.clone(),
1640 operation_id: operation_id.clone(),
1641 request_id: format!("group-e2ee-key-package-{operation_id}"),
1642 key_package_id: request
1643 .key_package_id
1644 .as_deref()
1645 .map(str::trim)
1646 .filter(|value| !value.is_empty())
1647 .map(str::to_owned),
1648 purpose: Some(request.purpose.as_str().to_owned()),
1649 group_did: request
1650 .group
1651 .as_ref()
1652 .map(|group| group.as_str().to_owned()),
1653 };
1654 let mut group_key_package = crate::internal::runtime::worker::run_blocking(move || {
1655 provider
1656 .generate_key_package(input)
1657 .map(|output| output.group_key_package)
1658 })
1659 .await
1660 .map_err(|err| crate::ImError::Internal {
1661 message: format!("group E2EE key package generation worker failed: {err}"),
1662 })??;
1663 group_key_package.did_wba_binding =
1664 sign_group_key_package_binding(credentials, owner_did, group_key_package.did_wba_binding)?;
1665 let key_package_id = group_key_package.key_package_id.clone();
1666 let params =
1667 crate::internal::group_e2ee::wire::build_group_e2ee_publish_key_package_rpc_params(
1668 credentials,
1669 owner_did,
1670 service_did.as_str(),
1671 &group_key_package,
1672 &operation_id,
1673 )?;
1674 Ok(PreparedGroupKeyPackagePublish {
1675 device_id,
1676 key_package_id,
1677 params,
1678 })
1679}
1680
1681fn group_did(result: &super::GroupReadResult) -> Option<String> {
1682 result
1683 .group
1684 .as_ref()
1685 .map(|group| group.did.as_str().to_string())
1686 .or_else(|| {
1687 result
1688 .response_json()
1689 .and_then(|raw| raw.get("group_did").or_else(|| raw.get("did")))
1690 .and_then(serde_json::Value::as_str)
1691 .map(str::trim)
1692 .filter(|value| !value.is_empty())
1693 .map(str::to_string)
1694 })
1695}
1696
1697#[cfg(feature = "group-e2ee")]
1698fn group_state_ref_from_result(
1699 group_did: &str,
1700 result: &super::GroupReadResult,
1701) -> Option<anp::group_e2ee::GroupStateRef> {
1702 result.raw_response().and_then(|raw| {
1703 crate::internal::group_e2ee::state_ref::group_state_ref_from_group_response(group_did, raw)
1704 })
1705}
1706
1707#[cfg(feature = "group-e2ee")]
1708fn group_state_ref_from_current_group(
1709 service: &GroupService<'_>,
1710 group_did: &str,
1711) -> Option<anp::group_e2ee::GroupStateRef> {
1712 let group = crate::ids::GroupRef::parse(group_did).ok()?;
1713 service
1714 .get(group)
1715 .ok()
1716 .and_then(|result| group_state_ref_from_result(group_did, &result))
1717}
1718
1719#[cfg(feature = "group-e2ee")]
1720async fn group_state_ref_from_current_group_async(
1721 service: &GroupService<'_>,
1722 group_did: &str,
1723) -> Option<anp::group_e2ee::GroupStateRef> {
1724 let group = crate::ids::GroupRef::parse(group_did).ok()?;
1725 service
1726 .get_async(group)
1727 .await
1728 .ok()
1729 .and_then(|result| group_state_ref_from_result(group_did, &result))
1730}
1731
1732fn resolve_group_member(
1733 client: &crate::core::ImClient,
1734 member: &super::GroupMemberRef,
1735) -> crate::ImResult<super::GroupMemberResolution> {
1736 if member.is_did() {
1737 return Ok(super::GroupMemberResolution {
1738 did: member.as_did()?,
1739 handle: None,
1740 });
1741 }
1742 let handle = crate::ids::Handle::parse(member.as_str(), "")?;
1743 let lookup = client.directory().lookup_handle(handle)?;
1744 Ok(super::GroupMemberResolution {
1745 did: lookup.did,
1746 handle: Some(lookup.handle),
1747 })
1748}
1749
1750async fn resolve_group_member_async(
1751 client: &crate::core::ImClient,
1752 member: &super::GroupMemberRef,
1753) -> crate::ImResult<super::GroupMemberResolution> {
1754 if member.is_did() {
1755 return Ok(super::GroupMemberResolution {
1756 did: member.as_did()?,
1757 handle: None,
1758 });
1759 }
1760 let handle = crate::ids::Handle::parse(member.as_str(), "")?;
1761 let lookup = client.directory().lookup_handle_async(handle).await?;
1762 Ok(super::GroupMemberResolution {
1763 did: lookup.did,
1764 handle: Some(lookup.handle),
1765 })
1766}
1767
1768fn resolved_group_member_request(
1769 request: super::GroupMemberMutationRequest,
1770 member: &super::GroupMemberResolution,
1771) -> super::GroupMemberMutationRequest {
1772 super::GroupMemberMutationRequest {
1773 group: request.group,
1774 member: super::GroupMemberRef::from(member.did.clone()),
1775 role: request.role,
1776 reason_text: request.reason_text,
1777 leave_request_id: request.leave_request_id,
1778 security: request.security,
1779 }
1780}
1781
1782fn project_group_system_event_best_effort(
1783 client: &crate::core::ImClient,
1784 group: &str,
1785 result: &mut super::GroupReadResult,
1786) {
1787 if let Err(error) =
1788 crate::internal::group_system_events::persist_group_read_result(client, group, result)
1789 {
1790 result.push_warning(format!("group_system_event_projection_failed:{error}"));
1791 }
1792}
1793
1794async fn project_group_system_event_best_effort_async(
1795 client: &crate::core::ImClient,
1796 group: &str,
1797 result: &mut super::GroupReadResult,
1798) {
1799 if let Err(error) =
1800 crate::internal::group_system_events::persist_group_read_result_async(client, group, result)
1801 .await
1802 {
1803 result.push_warning(format!("group_system_event_projection_failed:{error}"));
1804 }
1805}
1806
1807fn group_create_uses_e2ee(request: &super::GroupCreateRequest) -> bool {
1808 request.security.required()
1809 || request.e2ee
1810 || matches!(
1811 request.message_security_profile,
1812 Some(super::GroupMessageSecurityProfile::GroupE2ee)
1813 )
1814 || matches!(
1815 request.message_security_profile.as_ref(),
1816 Some(super::GroupMessageSecurityProfile::Custom(value))
1817 if value.trim() == "group-e2ee"
1818 )
1819}
1820
1821#[cfg(feature = "group-e2ee")]
1822fn sign_group_key_package_binding(
1823 credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1824 owner_did: &str,
1825 binding: serde_json::Value,
1826) -> crate::ImResult<serde_json::Value> {
1827 let leaf_signature_key_b64u = binding
1828 .get("leaf_signature_key_b64u")
1829 .and_then(serde_json::Value::as_str)
1830 .filter(|value| !value.trim().is_empty())
1831 .ok_or_else(|| crate::ImError::Serialization {
1832 detail: "group KeyPackage binding is missing leaf_signature_key_b64u".to_owned(),
1833 })?;
1834 let issued_at = binding
1835 .get("issued_at")
1836 .and_then(serde_json::Value::as_str)
1837 .filter(|value| !value.trim().is_empty())
1838 .ok_or_else(|| crate::ImError::Serialization {
1839 detail: "group KeyPackage binding is missing issued_at".to_owned(),
1840 })?;
1841 let expires_at = binding
1842 .get("expires_at")
1843 .and_then(serde_json::Value::as_str)
1844 .filter(|value| !value.trim().is_empty())
1845 .ok_or_else(|| crate::ImError::Serialization {
1846 detail: "group KeyPackage binding is missing expires_at".to_owned(),
1847 })?;
1848 let did_document =
1849 credentials
1850 .did_document
1851 .as_ref()
1852 .ok_or_else(|| crate::ImError::Serialization {
1853 detail: "identity is missing a DID document for group KeyPackage binding"
1854 .to_owned(),
1855 })?;
1856 let verification_method = assertion_verification_method_id_from_document(did_document)
1857 .ok_or_else(|| crate::ImError::Serialization {
1858 detail: "identity is missing an assertion verification method".to_owned(),
1859 })?;
1860 let private_key =
1861 crate::internal::proof::origin::load_private_key_material(&credentials.key1_private_pem)?;
1862 anp::proof::generate_did_wba_binding(
1863 owner_did,
1864 &verification_method,
1865 leaf_signature_key_b64u,
1866 &private_key,
1867 issued_at,
1868 expires_at,
1869 Some(issued_at.to_owned()),
1870 )
1871 .map_err(|err| crate::ImError::Serialization {
1872 detail: format!("generate group KeyPackage DID WBA binding proof: {err}"),
1873 })
1874}
1875
1876#[cfg(feature = "group-e2ee")]
1877fn assertion_verification_method_id_from_document(
1878 did_document: &serde_json::Value,
1879) -> Option<String> {
1880 did_document
1881 .get("assertionMethod")
1882 .and_then(serde_json::Value::as_array)
1883 .and_then(|methods| methods.first())
1884 .and_then(|method| {
1885 method
1886 .as_str()
1887 .or_else(|| method.get("id").and_then(serde_json::Value::as_str))
1888 })
1889 .map(str::to_owned)
1890 .or_else(|| {
1891 crate::internal::proof::origin::verification_method_id_from_document(did_document)
1892 })
1893}
1894
1895#[cfg(all(test, feature = "group-e2ee"))]
1896fn verify_group_key_package_binding_contract(
1897 binding: &serde_json::Value,
1898 did_document: &serde_json::Value,
1899) -> crate::ImResult<()> {
1900 anp::proof::verify_did_wba_binding(
1901 binding,
1902 did_document,
1903 anp::proof::DidWbaBindingVerificationOptions {
1904 expected_leaf_signature_key_b64u: binding
1905 .get("leaf_signature_key_b64u")
1906 .and_then(serde_json::Value::as_str)
1907 .map(str::to_owned),
1908 expected_credential_identity: binding
1909 .get("agent_did")
1910 .and_then(serde_json::Value::as_str)
1911 .map(str::to_owned),
1912 ..Default::default()
1913 },
1914 )
1915 .map_err(|err| crate::ImError::Serialization {
1916 detail: format!("verify group KeyPackage DID WBA binding proof: {err}"),
1917 })
1918}
1919
1920#[cfg(feature = "group-e2ee")]
1921fn ensure_group_e2ee_service_available(
1922 client: &crate::core::ImClient,
1923 check_key_package: bool,
1924) -> crate::ImResult<()> {
1925 let session_provider = crate::internal::auth::session::FileSessionProvider::new(client);
1926 let mut transport = crate::internal::transport::CoreHttpTransport::new(client);
1927 crate::internal::group_e2ee::lifecycle::ensure_group_e2ee_service_available(
1928 client,
1929 &session_provider,
1930 &mut transport,
1931 crate::internal::group_e2ee::lifecycle::GroupE2eeServiceAvailabilityInput {
1932 credentials: None,
1933 service_did: None,
1934 check_key_package,
1935 },
1936 )
1937}
1938
1939#[cfg(feature = "group-e2ee")]
1940async fn ensure_group_e2ee_service_available_async(
1941 client: &crate::core::ImClient,
1942 check_key_package: bool,
1943) -> crate::ImResult<()> {
1944 let session_provider = crate::internal::auth::session::FileSessionProvider::new(client);
1945 let mut transport = crate::internal::transport::CoreHttpTransport::new(client);
1946 crate::internal::group_e2ee::lifecycle::ensure_group_e2ee_service_available_async(
1947 client,
1948 &session_provider,
1949 &mut transport,
1950 crate::internal::group_e2ee::lifecycle::GroupE2eeServiceAvailabilityInput {
1951 credentials: None,
1952 service_did: None,
1953 check_key_package,
1954 },
1955 )
1956 .await
1957}
1958
1959#[cfg(feature = "group-e2ee")]
1960fn group_e2ee_service_did(client: &crate::core::ImClient) -> crate::ImResult<crate::ids::Did> {
1961 client
1962 .core_inner()
1963 .sdk_config()
1964 .anp_service_did
1965 .clone()
1966 .ok_or_else(|| {
1967 crate::ImError::invalid_input(
1968 Some("anp_service_did".to_owned()),
1969 "group E2EE key package publish requires ImCoreConfig.anp_service_did",
1970 )
1971 })
1972}
1973
1974#[cfg(all(test, feature = "group-e2ee"))]
1975mod tests;