atagen

macltok

public
Star0
Watch0
96fdf90aacd6dc2b8bd3d63773234245b389d28c
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/transport.rsRust1060 lines32.6 KB
1
use std::sync::{
2
    Arc,
3
    atomic::{AtomicU64, Ordering},
4
};
5
use std::{
6
    fmt,
7
    pin::Pin,
8
    task::{Context, Poll},
9
};
10
11
use matrix_sdk::{Client, RoomMemberships};
12
use matrix_sdk_ui::{
13
    notification_client::{
14
        NotificationClient, NotificationProcessSetup, NotificationStatus, RawNotificationEvent,
15
    },
16
    sync_service::SyncService,
17
};
18
use ruma::{
19
    EventId, RoomId,
20
    events::{
21
        AnyGlobalAccountDataEventContent, AnyRoomAccountDataEvent, GlobalAccountDataEventType,
22
        RoomAccountDataEventType,
23
    },
24
    serde::Raw,
25
};
26
use thiserror::Error;
27
use tokio::sync::{OnceCell, mpsc, oneshot, watch};
28
29
use crate::{
30
    AccountCommand, Command, CommandId, ConnectionState, EncryptionState, Event, InteractiveAuth,
31
    InteractiveAuthOutcome, MemberFilter, MemberSummary, NotificationCommand, OwnDevice,
32
    PowerLevelChanges, PushNotification, PushNotificationEvent, PushNotificationStatus,
33
    RoomListFilter, RoomNotificationMode, RoomNotificationSettings, RoomPowerLevels, RoomSummary,
34
    encryption::encryption_state,
35
    model::{PushNotificationRoom, PushNotificationSender},
36
    normalise::{member_summary, room_summaries},
37
};
38
39
#[derive(Clone)]
40
pub struct ClientHandle {
41
    commands: mpsc::Sender<DriverRequest>,
42
    next_id: Arc<AtomicU64>,
43
    client: Client,
44
    connection: watch::Receiver<ConnectionState>,
45
    notification_client: Arc<OnceCell<NotificationClient>>,
46
    sync_service: Arc<SyncService>,
47
}
48
49
impl fmt::Debug for ClientHandle {
50
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51
        formatter
52
            .debug_struct("ClientHandle")
53
            .finish_non_exhaustive()
54
    }
55
}
56
57
impl ClientHandle {
58
    /// Current credentials after a refresh. Persist this snapshot atomically
59
    /// when `Event::SessionTokensChanged` arrives; it contains secrets.
60
    #[cfg(feature = "sqlite")]
61
    pub fn session_record(&self) -> Result<crate::SessionRecord, crate::AccountError> {
62
        crate::account::session_record(&self.client)
63
    }
64
65
    /// Queue a command and return its correlation ID, not its result. Drain
66
    /// `EventStream` concurrently and match one terminal result event by ID.
67
    /// A successful timeline send only means the SDK queue accepted it.
68
    pub async fn send(&self, command: Command) -> Result<CommandId, SendCommandError> {
69
        let id = self.next_command_id();
70
        self.commands
71
            .send(DriverRequest::Command(CommandEnvelope { id, command }))
72
            .await
73
            .map_err(|_| SendCommandError::Closed)?;
74
        Ok(id)
75
    }
76
77
    pub async fn delete_devices(
78
        &self,
79
        device_ids: Vec<ruma::OwnedDeviceId>,
80
        auth: Option<InteractiveAuth>,
81
    ) -> Result<InteractiveAuthOutcome, CommandResultError> {
82
        let (reply, result) = oneshot::channel();
83
        self.commands
84
            .send(DriverRequest::DeleteDevices {
85
                id: self.next_command_id(),
86
                device_ids,
87
                auth,
88
                reply,
89
            })
90
            .await
91
            .map_err(|_| SendCommandError::Closed)?;
92
        result.await.map_err(|_| CommandResultError::Closed)?
93
    }
94
95
    pub async fn rename_device(
96
        &self,
97
        device_id: ruma::OwnedDeviceId,
98
        display_name: impl Into<String>,
99
    ) -> Result<CommandId, SendCommandError> {
100
        self.send(Command::Account(AccountCommand::RenameDevice {
101
            device_id,
102
            display_name: display_name.into(),
103
        }))
104
        .await
105
    }
106
107
    pub async fn set_room_notification_mode(
108
        &self,
109
        room_id: ruma::OwnedRoomId,
110
        mode: RoomNotificationMode,
111
    ) -> Result<CommandId, SendCommandError> {
112
        self.send(Command::Notification(NotificationCommand::SetRoomMode {
113
            room_id,
114
            mode,
115
        }))
116
        .await
117
    }
118
119
    pub async fn reset_room_notification_mode(
120
        &self,
121
        room_id: ruma::OwnedRoomId,
122
    ) -> Result<CommandId, SendCommandError> {
123
        self.send(Command::Notification(NotificationCommand::ResetRoomMode(
124
            room_id,
125
        )))
126
        .await
127
    }
128
129
    pub async fn set_default_notification_mode(
130
        &self,
131
        encrypted: bool,
132
        one_to_one: bool,
133
        mode: RoomNotificationMode,
134
    ) -> Result<CommandId, SendCommandError> {
135
        self.send(Command::Notification(NotificationCommand::SetDefaultMode {
136
            encrypted,
137
            one_to_one,
138
            mode,
139
        }))
140
        .await
141
    }
142
143
    pub async fn add_notification_keyword(
144
        &self,
145
        keyword: impl Into<String>,
146
    ) -> Result<CommandId, SendCommandError> {
147
        self.send(Command::Notification(NotificationCommand::AddKeyword(
148
            keyword.into(),
149
        )))
150
        .await
151
    }
152
153
    pub async fn remove_notification_keyword(
154
        &self,
155
        keyword: impl Into<String>,
156
    ) -> Result<CommandId, SendCommandError> {
157
        self.send(Command::Notification(NotificationCommand::RemoveKeyword(
158
            keyword.into(),
159
        )))
160
        .await
161
    }
162
163
    pub async fn open_room(
164
        &self,
165
        room_id: ruma::OwnedRoomId,
166
    ) -> Result<CommandId, SendCommandError> {
167
        self.send(Command::Room(crate::RoomCommand::Open(room_id)))
168
            .await
169
    }
170
171
    pub async fn close_room(
172
        &self,
173
        room_id: ruma::OwnedRoomId,
174
    ) -> Result<CommandId, SendCommandError> {
175
        self.send(Command::Room(crate::RoomCommand::Close(room_id)))
176
            .await
177
    }
178
179
    pub async fn open_thread(
180
        &self,
181
        room_id: ruma::OwnedRoomId,
182
        root_event_id: ruma::OwnedEventId,
183
    ) -> Result<CommandId, SendCommandError> {
184
        self.send(Command::Timeline(crate::TimelineCommand::OpenThread {
185
            room_id,
186
            root_event_id,
187
        }))
188
        .await
189
    }
190
191
    pub async fn close_thread(
192
        &self,
193
        room_id: ruma::OwnedRoomId,
194
        root_event_id: ruma::OwnedEventId,
195
    ) -> Result<CommandId, SendCommandError> {
196
        self.send(Command::Timeline(crate::TimelineCommand::CloseThread {
197
            room_id,
198
            root_event_id,
199
        }))
200
        .await
201
    }
202
203
    pub async fn join_room(
204
        &self,
205
        room: ruma::OwnedRoomOrAliasId,
206
        via: Vec<ruma::OwnedServerName>,
207
    ) -> Result<ruma::OwnedRoomId, CommandResultError> {
208
        self.room_result(crate::RoomCommand::Join { room, via })
209
            .await
210
    }
211
212
    pub async fn leave_room(
213
        &self,
214
        room_id: ruma::OwnedRoomId,
215
    ) -> Result<CommandId, SendCommandError> {
216
        self.send(Command::Room(crate::RoomCommand::Leave(room_id)))
217
            .await
218
    }
219
220
    pub async fn forget_room(
221
        &self,
222
        room_id: ruma::OwnedRoomId,
223
    ) -> Result<CommandId, SendCommandError> {
224
        self.send(Command::Room(crate::RoomCommand::Forget(room_id)))
225
            .await
226
    }
227
228
    pub async fn accept_invite(
229
        &self,
230
        room_id: ruma::OwnedRoomId,
231
    ) -> Result<CommandId, SendCommandError> {
232
        self.send(Command::Room(crate::RoomCommand::AcceptInvite(room_id)))
233
            .await
234
    }
235
236
    pub async fn reject_invite(
237
        &self,
238
        room_id: ruma::OwnedRoomId,
239
    ) -> Result<CommandId, SendCommandError> {
240
        self.send(Command::Room(crate::RoomCommand::RejectInvite(room_id)))
241
            .await
242
    }
243
244
    pub async fn invite_user(
245
        &self,
246
        room_id: ruma::OwnedRoomId,
247
        user_id: ruma::OwnedUserId,
248
    ) -> Result<CommandId, SendCommandError> {
249
        self.send(Command::Room(crate::RoomCommand::InviteUser {
250
            room_id,
251
            user_id,
252
        }))
253
        .await
254
    }
255
256
    pub async fn kick_user(
257
        &self,
258
        room_id: ruma::OwnedRoomId,
259
        user_id: ruma::OwnedUserId,
260
        reason: Option<String>,
261
    ) -> Result<CommandId, SendCommandError> {
262
        self.send(Command::Room(crate::RoomCommand::KickUser {
263
            room_id,
264
            user_id,
265
            reason,
266
        }))
267
        .await
268
    }
269
270
    pub async fn ban_user(
271
        &self,
272
        room_id: ruma::OwnedRoomId,
273
        user_id: ruma::OwnedUserId,
274
        reason: Option<String>,
275
    ) -> Result<CommandId, SendCommandError> {
276
        self.send(Command::Room(crate::RoomCommand::BanUser {
277
            room_id,
278
            user_id,
279
            reason,
280
        }))
281
        .await
282
    }
283
284
    pub async fn unban_user(
285
        &self,
286
        room_id: ruma::OwnedRoomId,
287
        user_id: ruma::OwnedUserId,
288
        reason: Option<String>,
289
    ) -> Result<CommandId, SendCommandError> {
290
        self.send(Command::Room(crate::RoomCommand::UnbanUser {
291
            room_id,
292
            user_id,
293
            reason,
294
        }))
295
        .await
296
    }
297
298
    pub async fn set_power_levels(
299
        &self,
300
        room_id: ruma::OwnedRoomId,
301
        changes: PowerLevelChanges,
302
    ) -> Result<CommandId, SendCommandError> {
303
        self.send(Command::Room(crate::RoomCommand::SetPowerLevels {
304
            room_id,
305
            changes,
306
        }))
307
        .await
308
    }
309
310
    pub async fn set_user_power_level(
311
        &self,
312
        room_id: ruma::OwnedRoomId,
313
        user_id: ruma::OwnedUserId,
314
        level: i64,
315
    ) -> Result<CommandId, SendCommandError> {
316
        self.send(Command::Room(crate::RoomCommand::SetUserPowerLevel {
317
            room_id,
318
            user_id,
319
            level,
320
        }))
321
        .await
322
    }
323
324
    pub async fn create_room(
325
        &self,
326
        options: crate::CreateRoomOptions,
327
    ) -> Result<ruma::OwnedRoomId, CommandResultError> {
328
        self.room_result(crate::RoomCommand::Create(options)).await
329
    }
330
331
    pub async fn create_direct(
332
        &self,
333
        user_id: ruma::OwnedUserId,
334
    ) -> Result<ruma::OwnedRoomId, CommandResultError> {
335
        self.room_result(crate::RoomCommand::CreateDirect(user_id))
336
            .await
337
    }
338
339
    async fn room_result(
340
        &self,
341
        command: crate::RoomCommand,
342
    ) -> Result<ruma::OwnedRoomId, CommandResultError> {
343
        let (reply, result) = oneshot::channel();
344
        self.commands
345
            .send(DriverRequest::RoomResult {
346
                id: self.next_command_id(),
347
                command,
348
                reply,
349
            })
350
            .await
351
            .map_err(|_| SendCommandError::Closed)?;
352
        result.await.map_err(|_| CommandResultError::Closed)?
353
    }
354
355
    pub async fn cancel_send(
356
        &self,
357
        room_id: ruma::OwnedRoomId,
358
        transaction_id: ruma::OwnedTransactionId,
359
    ) -> Result<CommandId, SendCommandError> {
360
        self.send(Command::Room(crate::RoomCommand::CancelSend {
361
            room_id,
362
            transaction_id,
363
        }))
364
        .await
365
    }
366
367
    pub async fn retry_send(
368
        &self,
369
        room_id: ruma::OwnedRoomId,
370
        transaction_id: ruma::OwnedTransactionId,
371
    ) -> Result<CommandId, SendCommandError> {
372
        self.send(Command::Room(crate::RoomCommand::RetrySend {
373
            room_id,
374
            transaction_id,
375
        }))
376
        .await
377
    }
378
379
    pub async fn upload_media(
380
        &self,
381
        timeline_id: crate::TimelineId,
382
        filename: String,
383
        mimetype: String,
384
        bytes: Vec<u8>,
385
        caption: Option<String>,
386
    ) -> Result<CommandId, SendCommandError> {
387
        self.send(Command::Media(crate::MediaCommand::Upload {
388
            timeline_id,
389
            filename,
390
            mimetype,
391
            bytes,
392
            caption,
393
        }))
394
        .await
395
    }
396
397
    pub async fn upload_attachment(
398
        &self,
399
        upload: crate::AttachmentUpload,
400
    ) -> Result<CommandId, SendCommandError> {
401
        self.send(Command::Media(crate::MediaCommand::UploadAttachment(
402
            upload,
403
        )))
404
        .await
405
    }
406
407
    pub async fn create_key_backup(&self) -> Result<CommandId, SendCommandError> {
408
        self.send(Command::Encryption(
409
            crate::EncryptionCommand::CreateKeyBackup,
410
        ))
411
        .await
412
    }
413
414
    pub async fn enable_key_backup(&self) -> Result<CommandId, SendCommandError> {
415
        self.send(Command::Encryption(
416
            crate::EncryptionCommand::EnableKeyBackup,
417
        ))
418
        .await
419
    }
420
421
    pub async fn disable_key_backup(
422
        &self,
423
        delete_from_server: bool,
424
    ) -> Result<CommandId, SendCommandError> {
425
        self.send(Command::Encryption(
426
            crate::EncryptionCommand::DisableKeyBackup { delete_from_server },
427
        ))
428
        .await
429
    }
430
431
    pub async fn recover_key_backup(
432
        &self,
433
        recovery_key: impl Into<String>,
434
    ) -> Result<CommandId, SendCommandError> {
435
        self.send(Command::Encryption(
436
            crate::EncryptionCommand::RecoverKeyBackup(recovery_key.into()),
437
        ))
438
        .await
439
    }
440
441
    pub async fn start_verification(
442
        &self,
443
        target: crate::VerificationTarget,
444
    ) -> Result<CommandId, SendCommandError> {
445
        self.send(Command::Encryption(
446
            crate::EncryptionCommand::StartVerification(target),
447
        ))
448
        .await
449
    }
450
451
    pub fn try_send(&self, command: Command) -> Result<CommandId, SendCommandError> {
452
        let id = self.next_command_id();
453
        self.commands
454
            .try_send(DriverRequest::Command(CommandEnvelope { id, command }))
455
            .map_err(|error| match error {
456
                mpsc::error::TrySendError::Full(_) => SendCommandError::Full,
457
                mpsc::error::TrySendError::Closed(_) => SendCommandError::Closed,
458
            })?;
459
        Ok(id)
460
    }
461
462
    fn next_command_id(&self) -> CommandId {
463
        CommandId::new(self.next_id.fetch_add(1, Ordering::Relaxed))
464
    }
465
466
    pub async fn rooms(&self) -> Vec<RoomSummary> {
467
        room_summaries(&self.client).await
468
    }
469
470
    pub async fn rooms_filtered(&self, filter: &RoomListFilter) -> Vec<RoomSummary> {
471
        let mut room_filter = filter.clone();
472
        let search = room_filter
473
            .search
474
            .take()
475
            .map(|search| search.to_lowercase());
476
477
        self.rooms()
478
            .await
479
            .into_iter()
480
            .filter(|room| {
481
                room_filter.matches(room)
482
                    && search.as_ref().is_none_or(|search| {
483
                        room.name.to_lowercase().contains(search)
484
                            || room.room_id.as_str().to_lowercase().contains(search)
485
                    })
486
            })
487
            .collect()
488
    }
489
490
    pub async fn devices(&self) -> Result<Vec<OwnDevice>, ClientQueryError> {
491
        let current_device_id = self.client.device_id();
492
        Ok(self
493
            .client
494
            .devices()
495
            .await
496
            .map_err(|source| ClientQueryError::Sdk(source.into()))?
497
            .devices
498
            .into_iter()
499
            .map(|device| OwnDevice {
500
                current: current_device_id == Some(device.device_id.as_ref()),
501
                device_id: device.device_id,
502
                display_name: device.display_name,
503
                last_seen_ip: device.last_seen_ip,
504
                last_seen_timestamp_ms: device.last_seen_ts.map(|timestamp| timestamp.get().into()),
505
            })
506
            .collect())
507
    }
508
509
    pub async fn room_notification_settings(
510
        &self,
511
        room_id: &RoomId,
512
    ) -> Result<Option<RoomNotificationSettings>, ClientQueryError> {
513
        let room = self
514
            .client
515
            .get_room(room_id)
516
            .ok_or_else(|| ClientQueryError::RoomUnknown(room_id.to_owned()))?;
517
        let custom = room.user_defined_notification_mode().await;
518
        let mode = match custom {
519
            Some(mode) => mode,
520
            None => match room.notification_mode().await {
521
                Some(mode) => mode,
522
                None => return Ok(None),
523
            },
524
        };
525
        Ok(Some(RoomNotificationSettings {
526
            mode: crate::normalise::notification_mode(mode),
527
            custom: custom.is_some(),
528
        }))
529
    }
530
531
    pub async fn notification_keywords(&self) -> Vec<String> {
532
        self.client
533
            .notification_settings()
534
            .await
535
            .enabled_keywords()
536
            .await
537
            .into_iter()
538
            .collect()
539
    }
540
541
    pub async fn resolve_push_notification(
542
        &self,
543
        room_id: &RoomId,
544
        event_id: &EventId,
545
    ) -> Result<PushNotificationStatus, ClientQueryError> {
546
        let client = self
547
            .notification_client
548
            .get_or_try_init(|| async {
549
                NotificationClient::new(
550
                    self.client.clone(),
551
                    NotificationProcessSetup::SingleProcess {
552
                        sync_service: self.sync_service.clone(),
553
                    },
554
                )
555
                .await
556
            })
557
            .await
558
            .map_err(ClientQueryError::Notification)?;
559
        let status = client
560
            .get_notification(room_id, event_id)
561
            .await
562
            .map_err(ClientQueryError::Notification)?;
563
        Ok(normalise_push_notification(room_id, event_id, status))
564
    }
565
566
    pub async fn members(
567
        &self,
568
        room_id: &RoomId,
569
        filter: MemberFilter,
570
    ) -> Result<Vec<MemberSummary>, ClientQueryError> {
571
        let room = self
572
            .client
573
            .get_room(room_id)
574
            .ok_or_else(|| ClientQueryError::RoomUnknown(room_id.to_owned()))?;
575
        let memberships = match filter {
576
            MemberFilter::Active => RoomMemberships::ACTIVE,
577
            MemberFilter::Joined => RoomMemberships::JOIN,
578
            MemberFilter::Invited => RoomMemberships::INVITE,
579
            // An empty mask is the SDK's unfiltered wildcard: it matches every
580
            // membership, including custom states that `all()` would exclude.
581
            MemberFilter::All => RoomMemberships::empty(),
582
        };
583
        Ok(room
584
            .members(memberships)
585
            .await
586
            .map_err(ClientQueryError::Sdk)?
587
            .into_iter()
588
            .map(member_summary)
589
            .collect())
590
    }
591
592
    pub async fn room_power_levels(
593
        &self,
594
        room_id: &RoomId,
595
    ) -> Result<RoomPowerLevels, ClientQueryError> {
596
        let room = self
597
            .client
598
            .get_room(room_id)
599
            .ok_or_else(|| ClientQueryError::RoomUnknown(room_id.to_owned()))?;
600
        let levels = room
601
            .power_levels()
602
            .await
603
            .map_err(|source| ClientQueryError::Sdk(source.into()))?;
604
        Ok(RoomPowerLevels {
605
            ban: levels.ban.into(),
606
            invite: levels.invite.into(),
607
            kick: levels.kick.into(),
608
            redact: levels.redact.into(),
609
            events_default: levels.events_default.into(),
610
            state_default: levels.state_default.into(),
611
            users_default: levels.users_default.into(),
612
            room_notification: levels.notifications.room.into(),
613
            users: levels
614
                .users
615
                .into_iter()
616
                .map(|(user_id, level)| (user_id, level.into()))
617
                .collect(),
618
            events: levels
619
                .events
620
                .into_iter()
621
                .map(|(event_type, level)| (event_type.to_string(), level.into()))
622
                .collect(),
623
        })
624
    }
625
626
    pub async fn pending_send_ids(
627
        &self,
628
        room_id: &RoomId,
629
    ) -> Result<Vec<ruma::OwnedTransactionId>, ClientQueryError> {
630
        let room = self
631
            .client
632
            .get_room(room_id)
633
            .ok_or_else(|| ClientQueryError::RoomUnknown(room_id.to_owned()))?;
634
        let (echoes, _) = room
635
            .send_queue()
636
            .subscribe()
637
            .await
638
            .map_err(ClientQueryError::SendQueue)?;
639
        Ok(echoes
640
            .into_iter()
641
            .filter(|echo| {
642
                matches!(
643
                    echo.content,
644
                    matrix_sdk::send_queue::LocalEchoContent::Event { .. }
645
                )
646
            })
647
            .map(|echo| echo.transaction_id)
648
            .collect())
649
    }
650
651
    pub async fn global_account_data(
652
        &self,
653
        event_type: GlobalAccountDataEventType,
654
    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>, ClientQueryError> {
655
        self.client
656
            .account()
657
            .account_data_raw(event_type)
658
            .await
659
            .map_err(ClientQueryError::Sdk)
660
    }
661
662
    pub async fn set_global_account_data(
663
        &self,
664
        event_type: GlobalAccountDataEventType,
665
        content: Raw<AnyGlobalAccountDataEventContent>,
666
    ) -> Result<(), ClientQueryError> {
667
        let (reply, result) = oneshot::channel();
668
        self.commands
669
            .send(DriverRequest::SetGlobalAccountData {
670
                event_type,
671
                content,
672
                reply,
673
            })
674
            .await
675
            .map_err(|_| ClientQueryError::EngineStopped)?;
676
        result.await.map_err(|_| ClientQueryError::EngineStopped)?
677
    }
678
679
    pub async fn room_account_data(
680
        &self,
681
        room_id: &RoomId,
682
        event_type: RoomAccountDataEventType,
683
    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, ClientQueryError> {
684
        let room = self
685
            .client
686
            .get_room(room_id)
687
            .ok_or_else(|| ClientQueryError::RoomUnknown(room_id.to_owned()))?;
688
        room.account_data(event_type)
689
            .await
690
            .map_err(ClientQueryError::Sdk)
691
    }
692
693
    pub async fn set_room_account_data(
694
        &self,
695
        room_id: &RoomId,
696
        event_type: RoomAccountDataEventType,
697
        content: Raw<ruma::events::AnyRoomAccountDataEventContent>,
698
    ) -> Result<(), ClientQueryError> {
699
        let (reply, result) = oneshot::channel();
700
        self.commands
701
            .send(DriverRequest::SetRoomAccountData {
702
                room_id: room_id.to_owned(),
703
                event_type,
704
                content,
705
                reply,
706
            })
707
            .await
708
            .map_err(|_| ClientQueryError::EngineStopped)?;
709
        result.await.map_err(|_| ClientQueryError::EngineStopped)?
710
    }
711
712
    #[must_use]
713
    pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
714
        self.connection.clone()
715
    }
716
717
    #[must_use]
718
    pub fn encryption_state(&self) -> Option<EncryptionState> {
719
        let device_id = self.client.device_id()?.to_owned();
720
        let encryption = self.client.encryption();
721
        Some(encryption_state(
722
            device_id,
723
            encryption.recovery().state(),
724
            encryption.verification_state().get(),
725
            encryption.backups().state(),
726
        ))
727
    }
728
}
729
730
#[derive(Debug, Eq, PartialEq)]
731
pub(crate) struct CommandEnvelope {
732
    pub(crate) id: CommandId,
733
    pub(crate) command: Command,
734
}
735
736
#[derive(Debug)]
737
pub(crate) enum DriverRequest {
738
    Command(CommandEnvelope),
739
    DeleteDevices {
740
        id: CommandId,
741
        device_ids: Vec<ruma::OwnedDeviceId>,
742
        auth: Option<InteractiveAuth>,
743
        reply: oneshot::Sender<Result<InteractiveAuthOutcome, CommandResultError>>,
744
    },
745
    RoomResult {
746
        id: CommandId,
747
        command: crate::RoomCommand,
748
        reply: oneshot::Sender<Result<ruma::OwnedRoomId, CommandResultError>>,
749
    },
750
    SetGlobalAccountData {
751
        event_type: GlobalAccountDataEventType,
752
        content: Raw<AnyGlobalAccountDataEventContent>,
753
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
754
    },
755
    SetRoomAccountData {
756
        room_id: ruma::OwnedRoomId,
757
        event_type: RoomAccountDataEventType,
758
        content: Raw<ruma::events::AnyRoomAccountDataEventContent>,
759
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
760
    },
761
}
762
763
#[derive(Debug)]
764
pub struct EventStream {
765
    events: mpsc::Receiver<Event>,
766
}
767
768
impl EventStream {
769
    pub async fn recv(&mut self) -> Option<Event> {
770
        self.events.recv().await
771
    }
772
773
    pub fn try_recv(&mut self) -> Result<Event, mpsc::error::TryRecvError> {
774
        self.events.try_recv()
775
    }
776
}
777
778
impl futures_util::Stream for EventStream {
779
    type Item = Event;
780
781
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
782
        self.events.poll_recv(context)
783
    }
784
}
785
786
#[derive(Debug)]
787
pub(crate) struct DriverPort {
788
    commands: mpsc::Receiver<DriverRequest>,
789
    events: mpsc::Sender<Event>,
790
    connection: watch::Sender<ConnectionState>,
791
}
792
793
#[derive(Clone, Debug)]
794
pub(crate) struct EventSender(mpsc::Sender<Event>);
795
796
impl EventSender {
797
    pub(crate) async fn send(&self, event: Event) -> Result<(), SendEventError> {
798
        self.0.send(event).await.map_err(|_| SendEventError::Closed)
799
    }
800
801
    /// Resolves once every [`EventStream`] receiver has been dropped, so the
802
    /// engine can notice a lost event consumer inside a `select!`.
803
    pub(crate) async fn closed(&self) {
804
        self.0.closed().await;
805
    }
806
}
807
808
impl DriverPort {
809
    pub(crate) fn event_sender(&self) -> EventSender {
810
        EventSender(self.events.clone())
811
    }
812
813
    pub(crate) fn connection_sender(&self) -> watch::Sender<ConnectionState> {
814
        self.connection.clone()
815
    }
816
817
    pub async fn recv(&mut self) -> Option<DriverRequest> {
818
        self.commands.recv().await
819
    }
820
821
    pub async fn send(&self, event: Event) -> Result<(), SendEventError> {
822
        self.event_sender().send(event).await
823
    }
824
}
825
826
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
827
pub enum SendCommandError {
828
    #[error("the command channel is full")]
829
    Full,
830
    #[error("the engine has stopped")]
831
    Closed,
832
}
833
834
#[derive(Debug, Error)]
835
pub enum CommandResultError {
836
    #[error(transparent)]
837
    Send(#[from] SendCommandError),
838
    #[error("the engine stopped before the operation completed")]
839
    Closed,
840
    #[error("{0:?}")]
841
    Failed(crate::CommandFailure),
842
}
843
844
#[derive(Debug, Error)]
845
pub enum ClientQueryError {
846
    #[error("room {0} is not available in the local store")]
847
    RoomUnknown(ruma::OwnedRoomId),
848
    #[error("Matrix query failed")]
849
    Sdk(#[source] matrix_sdk::Error),
850
    #[error("reading the send queue failed")]
851
    SendQueue(#[source] matrix_sdk::send_queue::RoomSendQueueError),
852
    #[error("resolving the push notification failed")]
853
    Notification(#[source] matrix_sdk_ui::notification_client::Error),
854
    #[error("the engine stopped before the operation completed")]
855
    EngineStopped,
856
}
857
858
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
859
pub(crate) enum SendEventError {
860
    #[error("the event receiver has closed")]
861
    Closed,
862
}
863
864
#[must_use]
865
pub(crate) fn transport_pair(
866
    client: Client,
867
    sync_service: Arc<SyncService>,
868
    command_capacity: usize,
869
    event_capacity: usize,
870
) -> (ClientHandle, DriverPort, EventStream) {
871
    let (command_tx, command_rx) = mpsc::channel(command_capacity);
872
    let (event_tx, event_rx) = mpsc::channel(event_capacity);
873
    let (connection_tx, connection_rx) = watch::channel(ConnectionState::Idle);
874
    (
875
        ClientHandle {
876
            commands: command_tx,
877
            next_id: Arc::new(AtomicU64::new(1)),
878
            client,
879
            connection: connection_rx,
880
            notification_client: Arc::new(OnceCell::new()),
881
            sync_service,
882
        },
883
        DriverPort {
884
            commands: command_rx,
885
            events: event_tx,
886
            connection: connection_tx,
887
        },
888
        EventStream { events: event_rx },
889
    )
890
}
891
892
fn normalise_push_notification(
893
    room_id: &RoomId,
894
    event_id: &EventId,
895
    status: NotificationStatus,
896
) -> PushNotificationStatus {
897
    match status {
898
        NotificationStatus::Event(item) => {
899
            let event = match &item.raw_event {
900
                RawNotificationEvent::Timeline(event) => {
901
                    PushNotificationEvent::Timeline(event.clone())
902
                }
903
                RawNotificationEvent::Invite(event) => PushNotificationEvent::Invite(event.clone()),
904
            };
905
            PushNotificationStatus::Event(Box::new(PushNotification {
906
                event_id: event_id.to_owned(),
907
                event,
908
                sender: PushNotificationSender {
909
                    user_id: item.event.sender().to_owned(),
910
                    display_name: item.sender_display_name,
911
                    avatar_url: item.sender_avatar_url.map(Into::into),
912
                    name_ambiguous: item.is_sender_name_ambiguous,
913
                },
914
                room: PushNotificationRoom {
915
                    room_id: room_id.to_owned(),
916
                    name: item.room_computed_display_name,
917
                    avatar_url: item.room_avatar_url.map(Into::into),
918
                    canonical_alias: item
919
                        .room_canonical_alias
920
                        .as_deref()
921
                        .and_then(|alias| alias.parse().ok()),
922
                    topic: item.room_topic,
923
                    encrypted: item.is_room_encrypted,
924
                    direct: item.room_is_dm,
925
                    joined_members_count: item.joined_members_count,
926
                    service_members: item
927
                        .service_members
928
                        .into_iter()
929
                        .filter_map(|user_id| user_id.parse().ok())
930
                        .collect(),
931
                    active_service_members_count: item.active_service_members_count,
932
                    space: item.is_space,
933
                },
934
                noisy: item.is_noisy,
935
                has_mention: item.has_mention,
936
                thread_id: item.thread_id,
937
            }))
938
        }
939
        NotificationStatus::EventNotFound => PushNotificationStatus::NotFound,
940
        NotificationStatus::EventFilteredOut => PushNotificationStatus::FilteredOut,
941
        NotificationStatus::EventRedacted => PushNotificationStatus::Redacted,
942
    }
943
}
944
945
#[cfg(test)]
946
mod tests {
947
    use matrix_sdk::{Client, SessionMeta, SessionTokens, authentication::matrix::MatrixSession};
948
949
    use crate::{ConnectionState, LifecycleCommand};
950
951
    use super::*;
952
953
    async fn sync_service(client: Client) -> Arc<SyncService> {
954
        Arc::new(SyncService::builder(client).build().await.unwrap())
955
    }
956
957
    async fn test_client() -> Client {
958
        let client = Client::builder()
959
            .homeserver_url("http://localhost")
960
            .build()
961
            .await
962
            .unwrap();
963
        client
964
            .restore_session(MatrixSession {
965
                meta: SessionMeta {
966
                    user_id: "@test:localhost".parse().unwrap(),
967
                    device_id: "DEVICE".into(),
968
                },
969
                tokens: SessionTokens {
970
                    access_token: "access_token".to_owned(),
971
                    refresh_token: None,
972
                },
973
            })
974
            .await
975
            .unwrap();
976
        client
977
    }
978
979
    #[tokio::test]
980
    async fn transports_are_isolated() {
981
        let first_client = test_client().await;
982
        let second_client = test_client().await;
983
        let (first_handle, mut first_driver, mut first_events) =
984
            transport_pair(first_client.clone(), sync_service(first_client).await, 2, 2);
985
        let (_second_handle, mut second_driver, mut second_events) = transport_pair(
986
            second_client.clone(),
987
            sync_service(second_client).await,
988
            2,
989
            2,
990
        );
991
992
        let id = first_handle
993
            .send(Command::Lifecycle(LifecycleCommand::Shutdown))
994
            .await
995
            .unwrap();
996
        let request = first_driver.recv().await.unwrap();
997
        assert!(matches!(
998
            request,
999
            DriverRequest::Command(CommandEnvelope {
1000
                id: received_id,
1001
                command: Command::Lifecycle(LifecycleCommand::Shutdown),
1002
            }) if received_id == id
1003
        ));
1004
        assert!(second_driver.commands.try_recv().is_err());
1005
1006
        first_driver
1007
            .send(Event::ConnectionChanged(ConnectionState::Online))
1008
            .await
1009
            .unwrap();
1010
        assert_eq!(
1011
            first_events.recv().await,
1012
            Some(Event::ConnectionChanged(ConnectionState::Online))
1013
        );
1014
        assert!(second_events.try_recv().is_err());
1015
    }
1016
1017
    #[tokio::test]
1018
    async fn bounded_command_channel_reports_pressure() {
1019
        let client = test_client().await;
1020
        let (handle, _driver, _events) =
1021
            transport_pair(client.clone(), sync_service(client).await, 1, 1);
1022
1023
        handle
1024
            .try_send(Command::Lifecycle(LifecycleCommand::Shutdown))
1025
            .unwrap();
1026
        assert_eq!(
1027
            handle.try_send(Command::Lifecycle(LifecycleCommand::Shutdown)),
1028
            Err(SendCommandError::Full)
1029
        );
1030
    }
1031
1032
    #[tokio::test]
1033
    async fn cloned_handles_share_command_id_sequence() {
1034
        let client = test_client().await;
1035
        let (handle, mut driver, _events) =
1036
            transport_pair(client.clone(), sync_service(client).await, 1, 1);
1037
        let cloned = handle.clone();
1038
        let first = handle
1039
            .try_send(Command::Lifecycle(LifecycleCommand::Shutdown))
1040
            .unwrap();
1041
        assert_eq!(first.get(), 1);
1042
        assert_eq!(
1043
            cloned.try_send(Command::Lifecycle(LifecycleCommand::Shutdown)),
1044
            Err(SendCommandError::Full)
1045
        );
1046
        driver.recv().await.unwrap();
1047
1048
        let third = cloned
1049
            .send(Command::Lifecycle(LifecycleCommand::Shutdown))
1050
            .await
1051
            .unwrap();
1052
        assert_eq!(third.get(), 3);
1053
        driver.recv().await.unwrap();
1054
1055
        let fourth = handle
1056
            .try_send(Command::Lifecycle(LifecycleCommand::Shutdown))
1057
            .unwrap();
1058
        assert_eq!(fourth.get(), 4);
1059
    }
1060
}