1321 lines
40 KiB
Rust
1321 lines
40 KiB
Rust
#![forbid(unsafe_code)]
|
|
#![deny(clippy::disallowed_types)]
|
|
|
|
//! Mechanical UniFFI exports for Apple presentation code.
|
|
|
|
use std::{error::Error, fmt, sync::Arc};
|
|
|
|
use ironstorage::{
|
|
config::ConfigError,
|
|
mobile::{self, MobileShellState as StorageShellState, MobileTab as StorageTab},
|
|
mobile_authentication::{
|
|
MobileAuthenticationError as StorageAuthenticationError,
|
|
MobileAuthenticationErrorKind as StorageAuthenticationErrorKind,
|
|
MobileAuthenticationState as StorageAuthenticationState,
|
|
MobileEntryCopy as StorageEntryCopy,
|
|
},
|
|
mobile_entry::{
|
|
MobileEntryEditorFieldKind as StorageEntryEditorFieldKind,
|
|
MobileEntryEditorInput as StorageEntryEditorInput,
|
|
MobileEntryEditorPage as StorageEntryEditorPage,
|
|
MobileEntryEditorSession as StorageEntryEditorSession, MobileEntryPage as StorageEntryPage,
|
|
MobileEntrySectionKind as StorageEntrySectionKind,
|
|
},
|
|
mobile_home::{
|
|
self, MobileHomeChangeKind as StorageHomeChangeKind,
|
|
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
|
|
MobileHomeErrorKind as StorageHomeErrorKind, MobileHomeFreshness as StorageHomeFreshness,
|
|
MobileHomePhase as StorageHomePhase,
|
|
},
|
|
mobile_onboarding::{
|
|
self, MobileOnboardingError as StorageOnboardingError,
|
|
MobileOnboardingErrorKind as StorageOnboardingErrorKind,
|
|
MobileOnboardingPhase as StorageOnboardingPhase,
|
|
},
|
|
mobile_passwords::{
|
|
MobilePasswordError as StoragePasswordError,
|
|
MobilePasswordErrorKind as StoragePasswordErrorKind,
|
|
MobilePasswordRowKind as StoragePasswordRowKind,
|
|
},
|
|
};
|
|
|
|
uniffi::setup_scaffolding!();
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileTab {
|
|
Home,
|
|
Passwords,
|
|
Totp,
|
|
Preferences,
|
|
}
|
|
|
|
impl From<StorageTab> for MobileTab {
|
|
fn from(tab: StorageTab) -> Self {
|
|
match tab {
|
|
StorageTab::Home => Self::Home,
|
|
StorageTab::Passwords => Self::Passwords,
|
|
StorageTab::Totp => Self::Totp,
|
|
StorageTab::Preferences => Self::Preferences,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileTab> for StorageTab {
|
|
fn from(tab: MobileTab) -> Self {
|
|
match tab {
|
|
MobileTab::Home => Self::Home,
|
|
MobileTab::Passwords => Self::Passwords,
|
|
MobileTab::Totp => Self::Totp,
|
|
MobileTab::Preferences => Self::Preferences,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileShellState {
|
|
Loading,
|
|
Empty,
|
|
Ready,
|
|
Locked,
|
|
Error,
|
|
}
|
|
|
|
impl From<StorageShellState> for MobileShellState {
|
|
fn from(state: StorageShellState) -> Self {
|
|
match state {
|
|
StorageShellState::Loading => Self::Loading,
|
|
StorageShellState::Empty => Self::Empty,
|
|
StorageShellState::Ready => Self::Ready,
|
|
StorageShellState::Locked => Self::Locked,
|
|
StorageShellState::Error => Self::Error,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileShellState> for StorageShellState {
|
|
fn from(state: MobileShellState) -> Self {
|
|
match state {
|
|
MobileShellState::Loading => Self::Loading,
|
|
MobileShellState::Empty => Self::Empty,
|
|
MobileShellState::Ready => Self::Ready,
|
|
MobileShellState::Locked => Self::Locked,
|
|
MobileShellState::Error => Self::Error,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePage {
|
|
pub tab: MobileTab,
|
|
pub title: String,
|
|
pub system_image: String,
|
|
pub selected_system_image: String,
|
|
pub state: MobileShellState,
|
|
pub state_title: String,
|
|
pub state_detail: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileShell {
|
|
pub selected_tab: MobileTab,
|
|
pub pages: Vec<MobilePage>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeFreshness {
|
|
NeverRefreshed,
|
|
Cached,
|
|
Current,
|
|
}
|
|
|
|
impl From<StorageHomeFreshness> for MobileHomeFreshness {
|
|
fn from(freshness: StorageHomeFreshness) -> Self {
|
|
match freshness {
|
|
StorageHomeFreshness::NeverRefreshed => Self::NeverRefreshed,
|
|
StorageHomeFreshness::Cached => Self::Cached,
|
|
StorageHomeFreshness::Current => Self::Current,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeChangeKind {
|
|
PasswordEntry,
|
|
RecipientPolicy,
|
|
RecipientSignature,
|
|
RepositoryFile,
|
|
}
|
|
|
|
impl From<StorageHomeChangeKind> for MobileHomeChangeKind {
|
|
fn from(kind: StorageHomeChangeKind) -> Self {
|
|
match kind {
|
|
StorageHomeChangeKind::PasswordEntry => Self::PasswordEntry,
|
|
StorageHomeChangeKind::RecipientPolicy => Self::RecipientPolicy,
|
|
StorageHomeChangeKind::RecipientSignature => Self::RecipientSignature,
|
|
StorageHomeChangeKind::RepositoryFile => Self::RepositoryFile,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeChangeStatus {
|
|
Added,
|
|
Modified,
|
|
Deleted,
|
|
}
|
|
|
|
impl From<StorageHomeChangeStatus> for MobileHomeChangeStatus {
|
|
fn from(status: StorageHomeChangeStatus) -> Self {
|
|
match status {
|
|
StorageHomeChangeStatus::Added => Self::Added,
|
|
StorageHomeChangeStatus::Modified => Self::Modified,
|
|
StorageHomeChangeStatus::Deleted => Self::Deleted,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeSummaryRow {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub system_image: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeChange {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub system_image: String,
|
|
pub kind: MobileHomeChangeKind,
|
|
pub status: MobileHomeChangeStatus,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeCommit {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub system_image: String,
|
|
pub timestamp: i64,
|
|
pub changes: Vec<MobileHomeChange>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeNotice {
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub system_image: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomePage {
|
|
pub freshness: MobileHomeFreshness,
|
|
pub refreshed_at: Option<i64>,
|
|
pub summaries: Vec<MobileHomeSummaryRow>,
|
|
pub incoming: Vec<MobileHomeCommit>,
|
|
pub outgoing: Vec<MobileHomeCommit>,
|
|
pub incoming_total: u32,
|
|
pub outgoing_total: u32,
|
|
pub notice: Option<MobileHomeNotice>,
|
|
}
|
|
|
|
impl From<mobile_home::MobileHomePage> for MobileHomePage {
|
|
fn from(page: mobile_home::MobileHomePage) -> Self {
|
|
Self {
|
|
freshness: page.freshness().into(),
|
|
refreshed_at: page.refreshed_at(),
|
|
summaries: page
|
|
.summaries()
|
|
.iter()
|
|
.map(|row| MobileHomeSummaryRow {
|
|
id: row.id().to_owned(),
|
|
title: row.title().to_owned(),
|
|
detail: row.detail().to_owned(),
|
|
system_image: row.system_image().to_owned(),
|
|
})
|
|
.collect(),
|
|
incoming: page.incoming().iter().map(mobile_home_commit).collect(),
|
|
outgoing: page.outgoing().iter().map(mobile_home_commit).collect(),
|
|
incoming_total: u32::try_from(page.incoming_total()).unwrap_or(u32::MAX),
|
|
outgoing_total: u32::try_from(page.outgoing_total()).unwrap_or(u32::MAX),
|
|
notice: page.notice().map(|notice| MobileHomeNotice {
|
|
title: notice.title().to_owned(),
|
|
detail: notice.detail().to_owned(),
|
|
system_image: notice.system_image().to_owned(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mobile_home_commit(commit: &mobile_home::MobileHomeCommit) -> MobileHomeCommit {
|
|
MobileHomeCommit {
|
|
id: commit.id().to_owned(),
|
|
title: commit.title().to_owned(),
|
|
detail: commit.detail().to_owned(),
|
|
system_image: commit.system_image().to_owned(),
|
|
timestamp: commit.timestamp(),
|
|
changes: commit
|
|
.changes()
|
|
.iter()
|
|
.map(|change| MobileHomeChange {
|
|
id: change.id().to_owned(),
|
|
title: change.title().to_owned(),
|
|
detail: change.detail().to_owned(),
|
|
system_image: change.system_image().to_owned(),
|
|
kind: change.kind().into(),
|
|
status: change.status().into(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomePhase {
|
|
Validating,
|
|
Authenticating,
|
|
Receiving,
|
|
Integrating,
|
|
Finishing,
|
|
}
|
|
|
|
impl From<StorageHomePhase> for MobileHomePhase {
|
|
fn from(phase: StorageHomePhase) -> Self {
|
|
match phase {
|
|
StorageHomePhase::Validating => Self::Validating,
|
|
StorageHomePhase::Authenticating => Self::Authenticating,
|
|
StorageHomePhase::Receiving => Self::Receiving,
|
|
StorageHomePhase::Integrating => Self::Integrating,
|
|
StorageHomePhase::Finishing => Self::Finishing,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileHomeProgress {
|
|
pub phase: MobileHomePhase,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileHomeErrorKind {
|
|
MissingConfiguration,
|
|
Configuration,
|
|
Authentication,
|
|
Conflict,
|
|
DirtyLocalChanges,
|
|
Offline,
|
|
Interrupted,
|
|
SecureStorage,
|
|
Repository,
|
|
PartialProgress,
|
|
}
|
|
|
|
impl From<StorageHomeErrorKind> for MobileHomeErrorKind {
|
|
fn from(kind: StorageHomeErrorKind) -> Self {
|
|
match kind {
|
|
StorageHomeErrorKind::MissingConfiguration => Self::MissingConfiguration,
|
|
StorageHomeErrorKind::Configuration => Self::Configuration,
|
|
StorageHomeErrorKind::Authentication => Self::Authentication,
|
|
StorageHomeErrorKind::Conflict => Self::Conflict,
|
|
StorageHomeErrorKind::DirtyLocalChanges => Self::DirtyLocalChanges,
|
|
StorageHomeErrorKind::Offline => Self::Offline,
|
|
StorageHomeErrorKind::Interrupted => Self::Interrupted,
|
|
StorageHomeErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageHomeErrorKind::Repository => Self::Repository,
|
|
StorageHomeErrorKind::PartialProgress => Self::PartialProgress,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobileHomeFfiError {
|
|
Failed {
|
|
kind: MobileHomeErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Display for MobileHomeFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobileHomeFfiError {}
|
|
|
|
impl From<StorageHomeError> for MobileHomeFfiError {
|
|
fn from(error: StorageHomeError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileHomeOperation {
|
|
operation: mobile_home::MobileHomeOperation,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileHomeOperation {
|
|
pub fn progress(&self) -> MobileHomeProgress {
|
|
let progress = self.operation.progress();
|
|
MobileHomeProgress {
|
|
phase: progress.phase().into(),
|
|
title: progress.title().to_owned(),
|
|
detail: progress.detail().to_owned(),
|
|
}
|
|
}
|
|
|
|
pub fn cached(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.cached().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh_if_stale(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation
|
|
.refresh_if_stale()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn refresh(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.refresh().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
|
|
self.operation.pull().map(Into::into).map_err(Into::into)
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobilePasswordRowKind {
|
|
Directory,
|
|
Entry,
|
|
}
|
|
|
|
impl From<StoragePasswordRowKind> for MobilePasswordRowKind {
|
|
fn from(kind: StoragePasswordRowKind) -> Self {
|
|
match kind {
|
|
StoragePasswordRowKind::Directory => Self::Directory,
|
|
StoragePasswordRowKind::Entry => Self::Entry,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePasswordRow {
|
|
pub id: String,
|
|
pub path: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub system_image: String,
|
|
pub kind: MobilePasswordRowKind,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobilePasswordPage {
|
|
pub id: String,
|
|
pub path: String,
|
|
pub title: String,
|
|
pub rows: Vec<MobilePasswordRow>,
|
|
}
|
|
|
|
impl From<ironstorage::mobile_passwords::MobilePasswordPage> for MobilePasswordPage {
|
|
fn from(page: ironstorage::mobile_passwords::MobilePasswordPage) -> Self {
|
|
Self {
|
|
id: page.id().to_owned(),
|
|
path: page.path().to_owned(),
|
|
title: page.title().to_owned(),
|
|
rows: page
|
|
.rows()
|
|
.iter()
|
|
.map(|row| MobilePasswordRow {
|
|
id: row.id().to_owned(),
|
|
path: row.path().to_owned(),
|
|
title: row.title().to_owned(),
|
|
detail: row.detail().to_owned(),
|
|
system_image: row.system_image().to_owned(),
|
|
kind: row.kind().into(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobilePasswordErrorKind {
|
|
MissingConfiguration,
|
|
Configuration,
|
|
InvalidPath,
|
|
DirectoryMissing,
|
|
Repository,
|
|
}
|
|
|
|
impl From<StoragePasswordErrorKind> for MobilePasswordErrorKind {
|
|
fn from(kind: StoragePasswordErrorKind) -> Self {
|
|
match kind {
|
|
StoragePasswordErrorKind::MissingConfiguration => Self::MissingConfiguration,
|
|
StoragePasswordErrorKind::Configuration => Self::Configuration,
|
|
StoragePasswordErrorKind::InvalidPath => Self::InvalidPath,
|
|
StoragePasswordErrorKind::DirectoryMissing => Self::DirectoryMissing,
|
|
StoragePasswordErrorKind::Repository => Self::Repository,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobilePasswordFfiError {
|
|
Failed {
|
|
kind: MobilePasswordErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Display for MobilePasswordFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobilePasswordFfiError {}
|
|
|
|
impl From<StoragePasswordError> for MobilePasswordFfiError {
|
|
fn from(error: StoragePasswordError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileAuthenticationErrorKind {
|
|
PassphraseRequired,
|
|
InvalidPassphrase,
|
|
Cancelled,
|
|
BiometryUnavailable,
|
|
Configuration,
|
|
KeyMaterial,
|
|
Entry,
|
|
SecureStorage,
|
|
Expired,
|
|
Conflict,
|
|
}
|
|
|
|
impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
|
|
fn from(kind: StorageAuthenticationErrorKind) -> Self {
|
|
match kind {
|
|
StorageAuthenticationErrorKind::PassphraseRequired => Self::PassphraseRequired,
|
|
StorageAuthenticationErrorKind::InvalidPassphrase => Self::InvalidPassphrase,
|
|
StorageAuthenticationErrorKind::Cancelled => Self::Cancelled,
|
|
StorageAuthenticationErrorKind::BiometryUnavailable => Self::BiometryUnavailable,
|
|
StorageAuthenticationErrorKind::Configuration => Self::Configuration,
|
|
StorageAuthenticationErrorKind::KeyMaterial => Self::KeyMaterial,
|
|
StorageAuthenticationErrorKind::Entry => Self::Entry,
|
|
StorageAuthenticationErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageAuthenticationErrorKind::Expired => Self::Expired,
|
|
StorageAuthenticationErrorKind::Conflict => Self::Conflict,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileAuthenticationState {
|
|
pub unlocked: bool,
|
|
pub biometric_unlock_enabled: bool,
|
|
pub remaining_seconds: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileEntrySectionKind {
|
|
Password,
|
|
Details,
|
|
OneTimePassword,
|
|
Notes,
|
|
}
|
|
|
|
impl From<StorageEntrySectionKind> for MobileEntrySectionKind {
|
|
fn from(kind: StorageEntrySectionKind) -> Self {
|
|
match kind {
|
|
StorageEntrySectionKind::Password => Self::Password,
|
|
StorageEntrySectionKind::Details => Self::Details,
|
|
StorageEntrySectionKind::OneTimePassword => Self::OneTimePassword,
|
|
StorageEntrySectionKind::Notes => Self::Notes,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryField {
|
|
pub id: u64,
|
|
pub label: String,
|
|
pub system_image: String,
|
|
pub value: Option<String>,
|
|
pub masked_value: String,
|
|
pub detail: Option<String>,
|
|
pub diagnostic: Option<String>,
|
|
pub sensitive: bool,
|
|
pub multiline: bool,
|
|
pub selectable: bool,
|
|
pub editable: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntrySection {
|
|
pub kind: MobileEntrySectionKind,
|
|
pub title: String,
|
|
pub fields: Vec<MobileEntryField>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryPage {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub sections: Vec<MobileEntrySection>,
|
|
}
|
|
|
|
impl From<StorageEntryPage> for MobileEntryPage {
|
|
fn from(page: StorageEntryPage) -> Self {
|
|
Self {
|
|
id: page.id().to_owned(),
|
|
title: page.title().to_owned(),
|
|
sections: page
|
|
.sections()
|
|
.iter()
|
|
.map(|section| MobileEntrySection {
|
|
kind: section.kind().into(),
|
|
title: section.title().to_owned(),
|
|
fields: section
|
|
.fields()
|
|
.iter()
|
|
.map(|field| MobileEntryField {
|
|
id: field.id(),
|
|
label: field.label().to_owned(),
|
|
system_image: field.system_image().to_owned(),
|
|
value: field.value().map(str::to_owned),
|
|
masked_value: field.masked_value().to_owned(),
|
|
detail: field.detail().map(str::to_owned),
|
|
diagnostic: field.diagnostic().map(str::to_owned),
|
|
sensitive: field.sensitive(),
|
|
multiline: field.multiline(),
|
|
selectable: field.selectable(),
|
|
editable: field.editable(),
|
|
})
|
|
.collect(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryCopy {
|
|
pub value: String,
|
|
pub timeout_seconds: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileEntryEditorFieldKind {
|
|
Password,
|
|
Field,
|
|
Note,
|
|
OtpUri,
|
|
}
|
|
|
|
impl From<StorageEntryEditorFieldKind> for MobileEntryEditorFieldKind {
|
|
fn from(kind: StorageEntryEditorFieldKind) -> Self {
|
|
match kind {
|
|
StorageEntryEditorFieldKind::Password => Self::Password,
|
|
StorageEntryEditorFieldKind::Field => Self::Field,
|
|
StorageEntryEditorFieldKind::Note => Self::Note,
|
|
StorageEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileEntryEditorFieldKind> for StorageEntryEditorFieldKind {
|
|
fn from(kind: MobileEntryEditorFieldKind) -> Self {
|
|
match kind {
|
|
MobileEntryEditorFieldKind::Password => Self::Password,
|
|
MobileEntryEditorFieldKind::Field => Self::Field,
|
|
MobileEntryEditorFieldKind::Note => Self::Note,
|
|
MobileEntryEditorFieldKind::OtpUri => Self::OtpUri,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorField {
|
|
pub id: u64,
|
|
pub kind: MobileEntryEditorFieldKind,
|
|
pub name: Option<String>,
|
|
pub label: String,
|
|
pub system_image: String,
|
|
pub value: Option<String>,
|
|
pub masked_value: String,
|
|
pub diagnostic: Option<String>,
|
|
pub sensitive: bool,
|
|
pub multiline: bool,
|
|
pub name_editable: bool,
|
|
pub removable: bool,
|
|
pub reorderable: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorPage {
|
|
pub path: String,
|
|
pub title: String,
|
|
pub fields: Vec<MobileEntryEditorField>,
|
|
pub dirty: bool,
|
|
pub creating: bool,
|
|
pub default_password_length: u32,
|
|
pub maximum_password_length: u32,
|
|
}
|
|
|
|
impl From<&StorageEntryEditorPage> for MobileEntryEditorPage {
|
|
fn from(page: &StorageEntryEditorPage) -> Self {
|
|
Self {
|
|
path: page.path().to_owned(),
|
|
title: page.title().to_owned(),
|
|
fields: page
|
|
.fields()
|
|
.iter()
|
|
.map(|field| MobileEntryEditorField {
|
|
id: field.id(),
|
|
kind: field.kind().into(),
|
|
name: field.name().map(str::to_owned),
|
|
label: field.label().to_owned(),
|
|
system_image: field.system_image().to_owned(),
|
|
value: field.value().map(str::to_owned),
|
|
masked_value: field.masked_value().to_owned(),
|
|
diagnostic: field.diagnostic().map(str::to_owned),
|
|
sensitive: field.sensitive(),
|
|
multiline: field.multiline(),
|
|
name_editable: field.name_editable(),
|
|
removable: field.removable(),
|
|
reorderable: field.reorderable(),
|
|
})
|
|
.collect(),
|
|
dirty: page.dirty(),
|
|
creating: page.creating(),
|
|
default_password_length: page.default_password_length(),
|
|
maximum_password_length: page.maximum_password_length(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorSession {
|
|
pub id: u64,
|
|
pub page: MobileEntryEditorPage,
|
|
}
|
|
|
|
impl From<StorageEntryEditorSession> for MobileEntryEditorSession {
|
|
fn from(session: StorageEntryEditorSession) -> Self {
|
|
Self {
|
|
id: session.id(),
|
|
page: session.page().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryEditorInput {
|
|
pub id: u64,
|
|
pub name: Option<String>,
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
impl From<MobileEntryEditorInput> for StorageEntryEditorInput {
|
|
fn from(input: MobileEntryEditorInput) -> Self {
|
|
Self::new(input.id, input.name, input.value)
|
|
}
|
|
}
|
|
|
|
impl From<StorageEntryCopy> for MobileEntryCopy {
|
|
fn from(copy: StorageEntryCopy) -> Self {
|
|
Self {
|
|
value: copy.value().to_owned(),
|
|
timeout_seconds: copy.timeout_seconds(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StorageAuthenticationState> for MobileAuthenticationState {
|
|
fn from(state: StorageAuthenticationState) -> Self {
|
|
Self {
|
|
unlocked: state.unlocked(),
|
|
biometric_unlock_enabled: state.biometric_unlock_enabled(),
|
|
remaining_seconds: state.remaining_seconds(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobileAuthenticationFfiError {
|
|
Failed {
|
|
kind: MobileAuthenticationErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Display for MobileAuthenticationFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobileAuthenticationFfiError {}
|
|
|
|
impl From<StorageAuthenticationError> for MobileAuthenticationFfiError {
|
|
fn from(error: StorageAuthenticationError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileAuthentication {
|
|
authentication: ironstorage::mobile_authentication::MobileAuthentication,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileAuthentication {
|
|
pub fn state(&self) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.state()
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn unlock_entry(
|
|
&self,
|
|
path: String,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.unlock_entry(
|
|
&path,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_biometric_unlock(
|
|
&self,
|
|
enabled: bool,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_biometric_unlock(enabled)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn touch_user_activity(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.touch_user_activity()
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn entry_page(
|
|
&self,
|
|
path: String,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.entry_page(&path)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn reveal_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
) -> Result<String, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.reveal_entry_field(&path, field)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_entry_field(&path, field)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn replace_entry_field(
|
|
&self,
|
|
path: String,
|
|
field: u64,
|
|
value: String,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.replace_entry_field(&path, field, value)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn begin_entry_editor(
|
|
&self,
|
|
path: String,
|
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.begin_entry_editor(&path)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn begin_create_entry(
|
|
&self,
|
|
directory: String,
|
|
name: String,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileEntryEditorSession, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.begin_create_entry(
|
|
&directory,
|
|
&name,
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.entry_editor(editor)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn update_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
fields: Vec<MobileEntryEditorInput>,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.update_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn add_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
kind: MobileEntryEditorFieldKind,
|
|
name: Option<String>,
|
|
value: String,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.add_entry_editor_field(editor, kind.into(), name, value)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn remove_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
field: u64,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.remove_entry_editor_field(editor, field)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn reorder_entry_editor_field(
|
|
&self,
|
|
editor: u64,
|
|
field: u64,
|
|
index: u32,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.reorder_entry_editor_field(editor, field, index as usize)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn generate_entry_editor_password(
|
|
&self,
|
|
editor: u64,
|
|
length: Option<u32>,
|
|
no_symbols: bool,
|
|
) -> Result<MobileEntryEditorPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.generate_entry_editor_password(editor, length, no_symbols)
|
|
.map(|page| (&page).into())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn save_entry_editor(
|
|
&self,
|
|
editor: u64,
|
|
fields: Vec<MobileEntryEditorInput>,
|
|
) -> Result<MobileEntryPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.save_entry_editor(editor, fields.into_iter().map(Into::into).collect())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn discard_entry_editor(&self, editor: u64) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.discard_entry_editor(editor)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication.manual_lock().map_err(Into::into)
|
|
}
|
|
|
|
pub fn cancel(&self) -> Result<(), MobileAuthenticationFfiError> {
|
|
self.authentication.cancel().map_err(Into::into)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileOnboardingPhase {
|
|
Validating,
|
|
Authenticating,
|
|
Receiving,
|
|
Integrating,
|
|
Finishing,
|
|
}
|
|
|
|
impl From<StorageOnboardingPhase> for MobileOnboardingPhase {
|
|
fn from(phase: StorageOnboardingPhase) -> Self {
|
|
match phase {
|
|
StorageOnboardingPhase::Validating => Self::Validating,
|
|
StorageOnboardingPhase::Authenticating => Self::Authenticating,
|
|
StorageOnboardingPhase::Receiving => Self::Receiving,
|
|
StorageOnboardingPhase::Integrating => Self::Integrating,
|
|
StorageOnboardingPhase::Finishing => Self::Finishing,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingProgress {
|
|
pub phase: MobileOnboardingPhase,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingDiscovery {
|
|
pub branches: Vec<String>,
|
|
pub selected_branch: u32,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileOnboardingOutcome {
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileOnboardingErrorKind {
|
|
InvalidInput,
|
|
UnsupportedRemote,
|
|
Authentication,
|
|
Repository,
|
|
ExistingClone,
|
|
Interrupted,
|
|
SecureStorage,
|
|
Configuration,
|
|
AlreadyConfigured,
|
|
}
|
|
|
|
impl From<StorageOnboardingErrorKind> for MobileOnboardingErrorKind {
|
|
fn from(kind: StorageOnboardingErrorKind) -> Self {
|
|
match kind {
|
|
StorageOnboardingErrorKind::InvalidInput => Self::InvalidInput,
|
|
StorageOnboardingErrorKind::UnsupportedRemote => Self::UnsupportedRemote,
|
|
StorageOnboardingErrorKind::Authentication => Self::Authentication,
|
|
StorageOnboardingErrorKind::Repository => Self::Repository,
|
|
StorageOnboardingErrorKind::ExistingClone => Self::ExistingClone,
|
|
StorageOnboardingErrorKind::Interrupted => Self::Interrupted,
|
|
StorageOnboardingErrorKind::SecureStorage => Self::SecureStorage,
|
|
StorageOnboardingErrorKind::Configuration => Self::Configuration,
|
|
StorageOnboardingErrorKind::AlreadyConfigured => Self::AlreadyConfigured,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobileOnboardingFfiError {
|
|
Failed {
|
|
kind: MobileOnboardingErrorKind,
|
|
title: String,
|
|
detail: String,
|
|
},
|
|
}
|
|
|
|
impl fmt::Display for MobileOnboardingFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobileOnboardingFfiError {}
|
|
|
|
impl From<StorageOnboardingError> for MobileOnboardingFfiError {
|
|
fn from(error: StorageOnboardingError) -> Self {
|
|
Self::Failed {
|
|
kind: error.kind().into(),
|
|
title: error.title().to_owned(),
|
|
detail: error.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation,
|
|
request: mobile_onboarding::MobileOnboardingRequest,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileOnboardingOperation {
|
|
pub fn progress(&self) -> MobileOnboardingProgress {
|
|
let progress = self.operation.progress();
|
|
MobileOnboardingProgress {
|
|
phase: progress.phase().into(),
|
|
title: progress.title().to_owned(),
|
|
detail: progress.detail().to_owned(),
|
|
}
|
|
}
|
|
|
|
pub fn discover(&self) -> Result<MobileOnboardingDiscovery, MobileOnboardingFfiError> {
|
|
let discovery = self.operation.discover(&self.request)?;
|
|
Ok(MobileOnboardingDiscovery {
|
|
branches: discovery.branches().to_vec(),
|
|
selected_branch: u32::try_from(discovery.selected_branch()).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
pub fn setup(
|
|
&self,
|
|
branch: String,
|
|
use_existing: bool,
|
|
) -> Result<MobileOnboardingOutcome, MobileOnboardingFfiError> {
|
|
let outcome = self.operation.setup(&self.request, &branch, use_existing)?;
|
|
Ok(MobileOnboardingOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
impl From<mobile::MobileShell> for MobileShell {
|
|
fn from(shell: mobile::MobileShell) -> Self {
|
|
Self {
|
|
selected_tab: shell.selected_tab().into(),
|
|
pages: shell
|
|
.pages()
|
|
.iter()
|
|
.map(|page| MobilePage {
|
|
tab: page.tab().into(),
|
|
title: page.title().to_owned(),
|
|
system_image: page.system_image().to_owned(),
|
|
selected_system_image: page.selected_system_image().to_owned(),
|
|
state: page.state().into(),
|
|
state_title: page.state_title().to_owned(),
|
|
state_detail: page.state_detail().to_owned(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobilePreferenceError {
|
|
Configuration { message: String },
|
|
}
|
|
|
|
impl fmt::Display for MobilePreferenceError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Configuration { message } => formatter.write_str(message),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobilePreferenceError {}
|
|
|
|
impl From<ConfigError> for MobilePreferenceError {
|
|
fn from(error: ConfigError) -> Self {
|
|
Self::Configuration {
|
|
message: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn product_name() -> String {
|
|
ironstorage::PRODUCT_NAME.to_owned()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_shell() -> MobileShell {
|
|
mobile::MobileShell::load().into()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_shell_fixture(state: MobileShellState) -> MobileShell {
|
|
mobile::MobileShell::fixture(state.into()).into()
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn set_selected_mobile_tab(tab: MobileTab) -> Result<(), MobilePreferenceError> {
|
|
mobile::store_selected_tab(tab.into()).map_err(Into::into)
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_home_operation() -> Arc<MobileHomeOperation> {
|
|
Arc::new(MobileHomeOperation {
|
|
operation: mobile_home::MobileHomeOperation::default(),
|
|
})
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_password_page(
|
|
path: Option<String>,
|
|
) -> Result<MobilePasswordPage, MobilePasswordFfiError> {
|
|
ironstorage::mobile_passwords::MobilePasswordPage::load(path.as_deref())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_authentication() -> Result<Arc<MobileAuthentication>, MobileAuthenticationFfiError> {
|
|
Ok(Arc::new(MobileAuthentication {
|
|
authentication: ironstorage::mobile_authentication::MobileAuthentication::load()?,
|
|
}))
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn mobile_onboarding_operation(
|
|
server_url: String,
|
|
account: String,
|
|
repository_path: String,
|
|
application_token: String,
|
|
) -> Result<Arc<MobileOnboardingOperation>, MobileOnboardingFfiError> {
|
|
Ok(Arc::new(MobileOnboardingOperation {
|
|
operation: mobile_onboarding::MobileOnboardingOperation::default(),
|
|
request: mobile_onboarding::MobileOnboardingRequest::new(
|
|
server_url,
|
|
account,
|
|
repository_path,
|
|
application_token.into_bytes(),
|
|
)?,
|
|
}))
|
|
}
|
|
|
|
#[uniffi::export]
|
|
pub fn replace_configured_mobile_application_token(
|
|
account: String,
|
|
application_token: String,
|
|
) -> Result<(), MobileOnboardingFfiError> {
|
|
mobile_onboarding::replace_configured_application_token(
|
|
account,
|
|
application_token.into_bytes(),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
MobileHomePhase, MobileOnboardingErrorKind, MobileOnboardingFfiError, MobileShellState,
|
|
MobileTab,
|
|
};
|
|
|
|
#[test]
|
|
fn bridge_reads_product_name_from_storage_crate() {
|
|
assert_eq!(super::product_name(), ironstorage::PRODUCT_NAME);
|
|
}
|
|
|
|
#[test]
|
|
fn bridge_exposes_every_view_ready_shell_fixture() {
|
|
for state in [
|
|
MobileShellState::Loading,
|
|
MobileShellState::Empty,
|
|
MobileShellState::Ready,
|
|
MobileShellState::Locked,
|
|
MobileShellState::Error,
|
|
] {
|
|
let shell = super::mobile_shell_fixture(state);
|
|
assert_eq!(shell.selected_tab, MobileTab::Home);
|
|
assert_eq!(shell.pages.len(), 4);
|
|
assert!(shell.pages.iter().all(|page| page.state == state));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bridge_rejects_non_https_before_creating_an_operation() {
|
|
let error = match super::mobile_onboarding_operation(
|
|
"ssh://example.test".to_owned(),
|
|
"alice".to_owned(),
|
|
"team/passwords".to_owned(),
|
|
"DO-NOT-RENDER".to_owned(),
|
|
) {
|
|
Ok(_) => panic!("SSH must be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
match error {
|
|
MobileOnboardingFfiError::Failed { kind, detail, .. } => {
|
|
assert_eq!(kind, MobileOnboardingErrorKind::UnsupportedRemote);
|
|
assert!(!detail.contains("DO-NOT-RENDER"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn home_operation_exposes_typed_progress_and_cancellation() {
|
|
let operation = super::mobile_home_operation();
|
|
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
|
|
operation.cancel();
|
|
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
|
|
}
|
|
}
|