1986 lines
59 KiB
Rust
1986 lines
59 KiB
Rust
#![forbid(unsafe_code)]
|
|
#![deny(clippy::disallowed_types)]
|
|
|
|
//! Mechanical UniFFI exports for Apple presentation code.
|
|
|
|
use std::{
|
|
error::Error,
|
|
fmt,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
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, MobileEntryPresentation as StorageEntryPresentation,
|
|
},
|
|
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_key_transfer::{
|
|
MobileKeyTransferError as StorageKeyTransferError,
|
|
MobileKeyTransferKey as StorageKeyTransferKey,
|
|
MobileKeyTransferKind as StorageKeyTransferKind,
|
|
MobileKeyTransferProgress as StorageKeyTransferProgress,
|
|
},
|
|
mobile_mutation::{
|
|
MobileMutationAction as StorageMutationAction,
|
|
MobileMutationOutcome as StorageMutationOutcome, MobileMutationPlan as StorageMutationPlan,
|
|
MobileMutationRequest as StorageMutationRequest,
|
|
},
|
|
mobile_onboarding::{
|
|
self, MobileOnboardingError as StorageOnboardingError,
|
|
MobileOnboardingErrorKind as StorageOnboardingErrorKind,
|
|
MobileOnboardingPhase as StorageOnboardingPhase,
|
|
},
|
|
mobile_passwords::{
|
|
MobilePasswordError as StoragePasswordError,
|
|
MobilePasswordErrorKind as StoragePasswordErrorKind,
|
|
MobilePasswordRowKind as StoragePasswordRowKind,
|
|
},
|
|
mobile_totp::{
|
|
MobileTotpDetail as StorageTotpDetail,
|
|
MobileTotpDiscoveryPhase as StorageTotpDiscoveryPhase,
|
|
MobileTotpOperation as StorageTotpOperation, MobileTotpPage as StorageTotpPage,
|
|
MobileWatchSnapshotState as StorageWatchSnapshotState,
|
|
MobileWatchSnapshotStatus as StorageWatchSnapshotStatus,
|
|
},
|
|
};
|
|
|
|
uniffi::setup_scaffolding!();
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileTab {
|
|
Home,
|
|
Passwords,
|
|
Search,
|
|
Totp,
|
|
Preferences,
|
|
}
|
|
|
|
impl From<StorageTab> for MobileTab {
|
|
fn from(tab: StorageTab) -> Self {
|
|
match tab {
|
|
StorageTab::Home => Self::Home,
|
|
StorageTab::Passwords => Self::Passwords,
|
|
StorageTab::Search => Self::Search,
|
|
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::Search => Self::Search,
|
|
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, uniffi::Record)]
|
|
pub struct MobileGitIdentity {
|
|
pub name: String,
|
|
pub email: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileMutationAction {
|
|
Move,
|
|
Copy,
|
|
Delete,
|
|
}
|
|
|
|
impl From<StorageMutationAction> for MobileMutationAction {
|
|
fn from(action: StorageMutationAction) -> Self {
|
|
match action {
|
|
StorageMutationAction::Move => Self::Move,
|
|
StorageMutationAction::Copy => Self::Copy,
|
|
StorageMutationAction::Delete => Self::Delete,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileMutationAction> for StorageMutationAction {
|
|
fn from(action: MobileMutationAction) -> Self {
|
|
match action {
|
|
MobileMutationAction::Move => Self::Move,
|
|
MobileMutationAction::Copy => Self::Copy,
|
|
MobileMutationAction::Delete => Self::Delete,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationDestination {
|
|
pub path: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub requires_overwrite: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationPlan {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub source_title: String,
|
|
pub revision: String,
|
|
pub destinations: Vec<MobileMutationDestination>,
|
|
pub has_open_editor: bool,
|
|
pub has_dirty_editor: bool,
|
|
}
|
|
|
|
impl From<StorageMutationPlan> for MobileMutationPlan {
|
|
fn from(plan: StorageMutationPlan) -> Self {
|
|
Self {
|
|
action: plan.action().into(),
|
|
source: plan.source().to_owned(),
|
|
source_title: plan.source_title().to_owned(),
|
|
revision: plan.revision().to_owned(),
|
|
destinations: plan
|
|
.destinations()
|
|
.iter()
|
|
.map(|destination| MobileMutationDestination {
|
|
path: destination.path().to_owned(),
|
|
title: destination.title().to_owned(),
|
|
detail: destination.detail().to_owned(),
|
|
requires_overwrite: destination.requires_overwrite(),
|
|
})
|
|
.collect(),
|
|
has_open_editor: plan.has_open_editor(),
|
|
has_dirty_editor: plan.has_dirty_editor(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationRequest {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub revision: String,
|
|
pub destination: Option<String>,
|
|
pub confirmed: bool,
|
|
pub overwrite: bool,
|
|
pub discard_editor: bool,
|
|
}
|
|
|
|
impl From<MobileMutationRequest> for StorageMutationRequest {
|
|
fn from(request: MobileMutationRequest) -> Self {
|
|
Self {
|
|
action: request.action.into(),
|
|
source: request.source,
|
|
revision: request.revision,
|
|
destination: request.destination,
|
|
confirmed: request.confirmed,
|
|
overwrite: request.overwrite,
|
|
discard_editor: request.discard_editor,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileMutationOutcome {
|
|
pub action: MobileMutationAction,
|
|
pub source: String,
|
|
pub destination: Option<String>,
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
impl From<StorageMutationOutcome> for MobileMutationOutcome {
|
|
fn from(outcome: StorageMutationOutcome) -> Self {
|
|
Self {
|
|
action: outcome.action().into(),
|
|
source: outcome.source().to_owned(),
|
|
destination: outcome.destination().map(str::to_owned),
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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 MobileWatchSnapshotState {
|
|
Unavailable,
|
|
Pending,
|
|
}
|
|
|
|
impl From<StorageWatchSnapshotState> for MobileWatchSnapshotState {
|
|
fn from(state: StorageWatchSnapshotState) -> Self {
|
|
match state {
|
|
StorageWatchSnapshotState::Unavailable => Self::Unavailable,
|
|
StorageWatchSnapshotState::Pending => Self::Pending,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileWatchSnapshotStatus {
|
|
pub state: MobileWatchSnapshotState,
|
|
pub detail: String,
|
|
}
|
|
|
|
impl From<&StorageWatchSnapshotStatus> for MobileWatchSnapshotStatus {
|
|
fn from(status: &StorageWatchSnapshotStatus) -> Self {
|
|
Self {
|
|
state: status.state().into(),
|
|
detail: status.detail().to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpRow {
|
|
pub path: String,
|
|
pub issuer: Option<String>,
|
|
pub account: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub shared_with_watch: bool,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpPage {
|
|
pub rows: Vec<MobileTotpRow>,
|
|
pub unavailable_entries: u32,
|
|
pub cache_notice: Option<String>,
|
|
pub watch: MobileWatchSnapshotStatus,
|
|
}
|
|
|
|
impl From<StorageTotpPage> for MobileTotpPage {
|
|
fn from(page: StorageTotpPage) -> Self {
|
|
Self {
|
|
rows: page
|
|
.rows()
|
|
.iter()
|
|
.map(|row| MobileTotpRow {
|
|
path: row.path().to_owned(),
|
|
issuer: row.issuer().map(str::to_owned),
|
|
account: row.account().to_owned(),
|
|
title: row.title().to_owned(),
|
|
detail: row.detail().to_owned(),
|
|
shared_with_watch: row.shared_with_watch(),
|
|
})
|
|
.collect(),
|
|
unavailable_entries: page.unavailable_entries(),
|
|
cache_notice: page.cache_notice().map(str::to_owned),
|
|
watch: page.watch().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileTotpDiscoveryPhase {
|
|
Preparing,
|
|
Inspecting,
|
|
Saving,
|
|
Complete,
|
|
Cancelled,
|
|
}
|
|
|
|
impl From<StorageTotpDiscoveryPhase> for MobileTotpDiscoveryPhase {
|
|
fn from(phase: StorageTotpDiscoveryPhase) -> Self {
|
|
match phase {
|
|
StorageTotpDiscoveryPhase::Preparing => Self::Preparing,
|
|
StorageTotpDiscoveryPhase::Inspecting => Self::Inspecting,
|
|
StorageTotpDiscoveryPhase::Saving => Self::Saving,
|
|
StorageTotpDiscoveryPhase::Complete => Self::Complete,
|
|
StorageTotpDiscoveryPhase::Cancelled => Self::Cancelled,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpDiscoveryProgress {
|
|
pub phase: MobileTotpDiscoveryPhase,
|
|
pub total: u32,
|
|
pub inspected: u32,
|
|
pub cache_hits: u32,
|
|
pub matches: u32,
|
|
pub unavailable: u32,
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileTotpOperation {
|
|
operation: StorageTotpOperation,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileTotpOperation {
|
|
pub fn progress(&self) -> MobileTotpDiscoveryProgress {
|
|
let progress = self.operation.progress();
|
|
MobileTotpDiscoveryProgress {
|
|
phase: progress.phase().into(),
|
|
total: progress.total(),
|
|
inspected: progress.inspected(),
|
|
cache_hits: progress.cache_hits(),
|
|
matches: progress.matches(),
|
|
unavailable: progress.unavailable(),
|
|
}
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.operation.cancel();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileTotpDetail {
|
|
pub path: String,
|
|
pub issuer: Option<String>,
|
|
pub account: String,
|
|
pub code: String,
|
|
pub valid_until: u64,
|
|
pub period: u64,
|
|
pub shared_with_watch: bool,
|
|
pub watch: MobileWatchSnapshotStatus,
|
|
}
|
|
|
|
impl From<StorageTotpDetail> for MobileTotpDetail {
|
|
fn from(detail: StorageTotpDetail) -> Self {
|
|
Self {
|
|
path: detail.path().to_owned(),
|
|
issuer: detail.issuer().map(str::to_owned),
|
|
account: detail.account().to_owned(),
|
|
code: String::from_utf8(detail.code().expose().to_vec())
|
|
.expect("storage-generated TOTP codes are ASCII"),
|
|
valid_until: detail.valid_until(),
|
|
period: detail.period(),
|
|
shared_with_watch: detail.shared_with_watch(),
|
|
watch: detail.watch().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileEntryPresentation {
|
|
pub page: MobileEntryPage,
|
|
pub totp: Option<MobileTotpDetail>,
|
|
pub cache_notice: Option<String>,
|
|
}
|
|
|
|
impl From<StorageEntryPresentation> for MobileEntryPresentation {
|
|
fn from(presentation: StorageEntryPresentation) -> Self {
|
|
let (page, totp, cache_notice) = presentation.into_parts();
|
|
Self {
|
|
page: page.into(),
|
|
totp: totp.map(Into::into),
|
|
cache_notice,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
|
pub enum MobileKeyTransferKind {
|
|
Public,
|
|
Private,
|
|
}
|
|
|
|
impl From<StorageKeyTransferKind> for MobileKeyTransferKind {
|
|
fn from(kind: StorageKeyTransferKind) -> Self {
|
|
match kind {
|
|
StorageKeyTransferKind::Public => Self::Public,
|
|
StorageKeyTransferKind::Private => Self::Private,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MobileKeyTransferKind> for StorageKeyTransferKind {
|
|
fn from(kind: MobileKeyTransferKind) -> Self {
|
|
match kind {
|
|
MobileKeyTransferKind::Public => Self::Public,
|
|
MobileKeyTransferKind::Private => Self::Private,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferKey {
|
|
pub fingerprint: String,
|
|
pub title: String,
|
|
pub detail: String,
|
|
pub kind: MobileKeyTransferKind,
|
|
pub requires_passphrase: bool,
|
|
}
|
|
|
|
impl From<&StorageKeyTransferKey> for MobileKeyTransferKey {
|
|
fn from(key: &StorageKeyTransferKey) -> Self {
|
|
Self {
|
|
fingerprint: key.fingerprint().to_owned(),
|
|
title: key.title().to_owned(),
|
|
detail: key.detail().to_owned(),
|
|
kind: key.kind().into(),
|
|
requires_passphrase: key.requires_passphrase(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferFrame {
|
|
pub sequence: u32,
|
|
pub total: u32,
|
|
pub width: u32,
|
|
pub modules: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferExport {
|
|
pub key: MobileKeyTransferKey,
|
|
pub frames: Vec<MobileKeyTransferFrame>,
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferProgress {
|
|
pub received: u32,
|
|
pub total: u32,
|
|
pub duplicate: bool,
|
|
pub key: Option<MobileKeyTransferKey>,
|
|
}
|
|
|
|
impl From<StorageKeyTransferProgress> for MobileKeyTransferProgress {
|
|
fn from(progress: StorageKeyTransferProgress) -> Self {
|
|
Self {
|
|
received: u32::try_from(progress.received()).unwrap_or(u32::MAX),
|
|
total: u32::try_from(progress.total()).unwrap_or(u32::MAX),
|
|
duplicate: progress.duplicate(),
|
|
key: progress.key().map(Into::into),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileKeyTransferOutcome {
|
|
pub title: String,
|
|
pub detail: String,
|
|
}
|
|
|
|
#[derive(Debug, uniffi::Error)]
|
|
pub enum MobileKeyTransferFfiError {
|
|
Failed { message: String },
|
|
}
|
|
|
|
impl fmt::Display for MobileKeyTransferFfiError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Failed { message } => formatter.write_str(message),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for MobileKeyTransferFfiError {}
|
|
|
|
impl From<StorageKeyTransferError> for MobileKeyTransferFfiError {
|
|
fn from(error: StorageKeyTransferError) -> Self {
|
|
Self::Failed {
|
|
message: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileKeyTransfer {
|
|
service: ironstorage::mobile_key_transfer::MobileKeyTransferService,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileKeyTransfer {
|
|
pub fn keys(&self) -> Vec<MobileKeyTransferKey> {
|
|
self.service.keys().iter().map(Into::into).collect()
|
|
}
|
|
|
|
pub fn export(
|
|
&self,
|
|
fingerprint: String,
|
|
kind: MobileKeyTransferKind,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileKeyTransferExport, MobileKeyTransferFfiError> {
|
|
let exported = self.service.export(
|
|
&fingerprint,
|
|
kind.into(),
|
|
passphrase.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)?;
|
|
let frames = exported
|
|
.frames()
|
|
.iter()
|
|
.map(|frame| {
|
|
let matrix = frame.matrix();
|
|
let mut modules = Vec::with_capacity(matrix.width() * matrix.width());
|
|
for y in 0..matrix.width() {
|
|
for x in 0..matrix.width() {
|
|
modules.push(u8::from(matrix.is_dark(x, y).unwrap_or(false)));
|
|
}
|
|
}
|
|
MobileKeyTransferFrame {
|
|
sequence: u32::try_from(frame.sequence()).unwrap_or(u32::MAX),
|
|
total: u32::try_from(frame.total()).unwrap_or(u32::MAX),
|
|
width: u32::try_from(matrix.width()).unwrap_or(u32::MAX),
|
|
modules,
|
|
}
|
|
})
|
|
.collect();
|
|
Ok(MobileKeyTransferExport {
|
|
key: exported.key().into(),
|
|
frames,
|
|
})
|
|
}
|
|
|
|
pub fn importer(&self) -> Arc<MobileKeyTransferImport> {
|
|
Arc::new(MobileKeyTransferImport {
|
|
importer: Mutex::new(self.service.importer()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(uniffi::Object)]
|
|
pub struct MobileKeyTransferImport {
|
|
importer: Mutex<ironstorage::mobile_key_transfer::MobileKeyTransferImport>,
|
|
}
|
|
|
|
#[uniffi::export]
|
|
impl MobileKeyTransferImport {
|
|
pub fn add_frame(
|
|
&self,
|
|
payload: String,
|
|
) -> Result<MobileKeyTransferProgress, MobileKeyTransferFfiError> {
|
|
self.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.add_frame(ironstorage::repository::SecretBytes::new(
|
|
payload.into_bytes(),
|
|
))
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn import(
|
|
&self,
|
|
passphrase: Option<String>,
|
|
make_default: bool,
|
|
) -> Result<MobileKeyTransferOutcome, MobileKeyTransferFfiError> {
|
|
let outcome = self
|
|
.importer
|
|
.lock()
|
|
.map_err(|_| MobileKeyTransferFfiError::Failed {
|
|
message: "the key-transfer session is unavailable".to_owned(),
|
|
})?
|
|
.import(
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
make_default,
|
|
)?;
|
|
Ok(MobileKeyTransferOutcome {
|
|
title: outcome.title().to_owned(),
|
|
detail: outcome.detail().to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[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 git_identity(&self) -> Result<MobileGitIdentity, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.git_identity()
|
|
.map(|identity| MobileGitIdentity {
|
|
name: identity.name().to_owned(),
|
|
email: identity.email().to_owned(),
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_git_identity(
|
|
&self,
|
|
name: String,
|
|
email: String,
|
|
) -> Result<MobileGitIdentity, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_git_identity(name, email)
|
|
.map(|identity| MobileGitIdentity {
|
|
name: identity.name().to_owned(),
|
|
email: identity.email().to_owned(),
|
|
})
|
|
.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,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryPresentation, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.entry_page(&path, unix_seconds)
|
|
.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 prepare_entry_mutation(
|
|
&self,
|
|
path: String,
|
|
action: MobileMutationAction,
|
|
) -> Result<MobileMutationPlan, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.prepare_entry_mutation(&path, action.into())
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn perform_entry_mutation(
|
|
&self,
|
|
request: MobileMutationRequest,
|
|
) -> Result<MobileMutationOutcome, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.perform_entry_mutation(request.into())
|
|
.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 unlock_totp(
|
|
&self,
|
|
passphrase: Option<String>,
|
|
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.unlock_totp(
|
|
passphrase
|
|
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
|
|
)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn totp_page(
|
|
&self,
|
|
operation: Arc<MobileTotpOperation>,
|
|
) -> Result<MobileTotpPage, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.totp_page(&operation.operation)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn cached_totp_page(&self) -> Result<Option<MobileTotpPage>, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.cached_totp_page()
|
|
.map(|page| page.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn search_cached_totp_page(
|
|
&self,
|
|
query: String,
|
|
) -> Result<Option<MobileTotpPage>, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.search_cached_totp_page(&query)
|
|
.map(|page| page.map(Into::into))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn totp_detail(
|
|
&self,
|
|
path: String,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileTotpDetail, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.totp_detail(&path, unix_seconds)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn copy_totp_code(
|
|
&self,
|
|
path: String,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileEntryCopy, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.copy_totp_code(&path, unix_seconds)
|
|
.map(Into::into)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub fn set_totp_watch_shared(
|
|
&self,
|
|
path: String,
|
|
shared: bool,
|
|
unix_seconds: u64,
|
|
) -> Result<MobileTotpDetail, MobileAuthenticationFfiError> {
|
|
self.authentication
|
|
.set_totp_watch_shared(&path, shared, unix_seconds)
|
|
.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_totp_operation() -> Arc<MobileTotpOperation> {
|
|
Arc::new(MobileTotpOperation {
|
|
operation: StorageTotpOperation::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_password_search(query: String) -> Result<MobilePasswordPage, MobilePasswordFfiError> {
|
|
ironstorage::mobile_passwords::MobilePasswordPage::search(&query)
|
|
.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_key_transfer() -> Result<Arc<MobileKeyTransfer>, MobileKeyTransferFfiError> {
|
|
Ok(Arc::new(MobileKeyTransfer {
|
|
service: ironstorage::mobile_key_transfer::MobileKeyTransferService::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(), 5);
|
|
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);
|
|
}
|
|
}
|