diff --git a/presage-cli/src/main.rs b/presage-cli/src/main.rs index dfbacb2d2..2f0e946f2 100644 --- a/presage-cli/src/main.rs +++ b/presage-cli/src/main.rs @@ -508,7 +508,9 @@ async fn run(subcommand: Cmd, config_store: S) -> anyhow::Re 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 12d6324e6..4c95783b1 100644 --- a/presage/src/errors.rs +++ b/presage/src/errors.rs @@ -70,6 +70,10 @@ pub enum Error { UnexpectedAttachmentChecksum, #[error("Unverified registration session (i.e. wrong verification code)")] UnverifiedRegistrationSession, + #[error("Failed to link secondary device")] + ServiceLinkError(#[from] libsignal_service::LinkError), + #[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 a17558672..4d26e632b 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 c3c2b044b..94400e4af 100644 --- a/presage/src/manager/registered.rs +++ b/presage/src/manager/registered.rs @@ -23,8 +23,8 @@ use libsignal_service::protocol::SenderCertificate; use libsignal_service::protocol::{PrivateKey, PublicKey}; use libsignal_service::provisioning::generate_registration_id; 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}; @@ -43,6 +43,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; @@ -52,6 +53,12 @@ use crate::{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 { @@ -967,6 +974,57 @@ 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, 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