diff --git a/presage-cli/src/main.rs b/presage-cli/src/main.rs index 646e7e183..d85fb61ff 100644 --- a/presage-cli/src/main.rs +++ b/presage-cli/src/main.rs @@ -514,7 +514,9 @@ async fn run(subcommand: Cmd, config_store: S) -> anyhow::Result<()> { async move { match provisioning_link_rx.await { Ok(url) => { - qr2term::print_qr(url.to_string()).expect("failed to render qrcode") + println!("Please scan in the QR code:"); + qr2term::print_qr(url.to_string()).expect("failed to render qrcode"); + println!("Alternatively, use the URL: {}", url); } Err(e) => log::error!("Error linking device: {e}"), } diff --git a/presage/src/errors.rs b/presage/src/errors.rs index dc9a583a7..d2d9ef813 100644 --- a/presage/src/errors.rs +++ b/presage/src/errors.rs @@ -72,6 +72,8 @@ pub enum Error { UnverifiedRegistrationSession, #[error("profile cipher error")] ProfileCipherError(#[from] libsignal_service::profile_cipher::ProfileCipherError), + #[error("An operation was requested that requires the registration to be primary, but it was only secondary")] + NotPrimaryDevice, } impl From for Error { diff --git a/presage/src/manager/mod.rs b/presage/src/manager/mod.rs index f0b59d17d..c11ed5e18 100644 --- a/presage/src/manager/mod.rs +++ b/presage/src/manager/mod.rs @@ -11,7 +11,7 @@ use rand::rngs::StdRng; pub use self::confirmation::Confirmation; pub use self::linking::Linking; -pub use self::registered::{ReceivingMode, Registered, RegistrationData}; +pub use self::registered::{ReceivingMode, Registered, RegistrationData, RegistrationType}; pub use self::registration::{Registration, RegistrationOptions}; /// Signal manager diff --git a/presage/src/manager/registered.rs b/presage/src/manager/registered.rs index c6d402c7d..3ffe9a6be 100644 --- a/presage/src/manager/registered.rs +++ b/presage/src/manager/registered.rs @@ -23,8 +23,8 @@ use libsignal_service::proto::{ use libsignal_service::protocol::{IdentityKeyStore, SenderCertificate}; use libsignal_service::provisioning::{generate_registration_id, ProvisioningError}; use libsignal_service::push_service::{ - AccountAttributes, DeviceCapabilities, PushService, ServiceError, ServiceIdType, ServiceIds, - WhoAmIResponse, DEFAULT_DEVICE_ID, + AccountAttributes, DeviceCapabilities, DeviceInfo, PushService, ServiceError, ServiceIdType, + ServiceIds, WhoAmIResponse, DEFAULT_DEVICE_ID, }; use libsignal_service::receiver::MessageReceiver; use libsignal_service::sender::{AttachmentSpec, AttachmentUploadError}; @@ -42,6 +42,7 @@ use rand::SeedableRng; use serde::{Deserialize, Serialize}; use sha2::Digest; use tokio::sync::Mutex; +use url::Url; use crate::cache::CacheCell; use crate::serde::serde_profile_key; @@ -51,6 +52,12 @@ use crate::{AvatarBytes, Error, Manager}; type ServiceCipher = cipher::ServiceCipher; type MessageSender = libsignal_service::prelude::MessageSender; +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RegistrationType { + Primary, + Secondary, +} + /// Manager state when the client is registered and can send and receive messages from Signal #[derive(Clone)] pub struct Registered { @@ -1216,6 +1223,62 @@ impl Manager { } } + /// Returns how this client was registered, either as a primary or secondary device. + pub fn registration_type(&self) -> RegistrationType { + if self.state.data.device_name.is_some() { + RegistrationType::Secondary + } else { + RegistrationType::Primary + } + } + + /// As a primary device, link a secondary device. + pub async fn link_secondary(&self, secondary: Url) -> Result<(), Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::NotPrimaryDevice); + } + + let credentials = self.credentials().ok_or(Error::NotYetRegisteredError)?; + let mut account_manager = AccountManager::new( + self.identified_push_service(), + Some(self.state.data.profile_key), + ); + let store = self.store(); + + account_manager + .link_device( + secondary, + &store.aci_protocol_store(), + &store.pni_protocol_store(), + credentials, + ) + .await?; + Ok(()) + } + + /// As a primary device, unlink a secondary device. + pub async fn unlink_secondary(&self, device_id: i64) -> Result<(), Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::NotPrimaryDevice); + } + self.identified_push_service() + .unlink_device(device_id) + .await?; + Ok(()) + } + + /// As a primary device, list all the devices. + // XXX: Also shows the current device? + pub async fn linked_devices(&self) -> Result, Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::::NotPrimaryDevice); + } + Ok(self.identified_push_service().devices().await?) + } + /// Deprecated methods /// Get a single contact by its UUID