Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for system messages, feature flags, accounts #1503

Merged
merged 25 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nym-vpn-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,9 @@ where
match self.account_storage.load_account().await {
Ok(account) => {
tracing::debug!("Our account id: {}", account.id());
self.account_state.set_mnemonic(MnemonicState::Stored).await;
self.account_state
.set_mnemonic(MnemonicState::Stored { id: account.id() })
.await;
Some(account)
}
Err(err) => {
Expand Down Expand Up @@ -428,7 +430,7 @@ where
}

self.account_state
.set_account(AccountState::from(account_summary.account.status))
.set_account(AccountState::from(account_summary.account))
.await;

self.account_state
Expand Down
41 changes: 29 additions & 12 deletions nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use std::{fmt, sync::Arc, time::Duration};

use nym_vpn_api_client::response::{
NymVpnAccountStatusResponse, NymVpnAccountSummarySubscription, NymVpnDeviceStatus,
NymVpnSubscriptionStatus,
NymVpnAccountResponse, NymVpnAccountStatusResponse, NymVpnAccountSummarySubscription,
NymVpnDeviceStatus, NymVpnSubscriptionStatus,
};
use serde::Serialize;
use tokio::sync::MutexGuard;
Expand Down Expand Up @@ -103,10 +103,15 @@ impl SharedAccountState {

pub(crate) async fn is_ready_to_register_device(&self) -> ReadyToRegisterDevice {
let state = self.lock().await.clone();
if state.mnemonic != Some(MnemonicState::Stored) {
if !state
.mnemonic
.map(|m| matches!(m, MnemonicState::Stored { .. }))
.unwrap_or(false)
{
return ReadyToRegisterDevice::NoMnemonicStored;
}
if state.account != Some(AccountState::Active) {
// if state.account.map(|a| !a.is_active()).unwrap_or(false) {
return ReadyToRegisterDevice::AccountNotActive;
}
if state.subscription != Some(SubscriptionState::Active) {
Expand Down Expand Up @@ -190,7 +195,7 @@ pub enum MnemonicState {
NotStored,

// The recovery phrase is stored locally
Stored,
Stored { id: String },
}

#[derive(Debug, Clone, PartialEq, Serialize)]
Expand Down Expand Up @@ -239,9 +244,21 @@ pub enum DeviceState {
}

impl AccountStateSummary {
pub fn account_id(&self) -> Option<String> {
match &self.mnemonic {
Some(MnemonicState::Stored { id }) => Some(id.clone()),
_ => None,
}
}

// If we are ready right right now.
fn is_ready_now(&self) -> ReadyToConnect {
if self.mnemonic != Some(MnemonicState::Stored) {
if !self
.mnemonic
.as_ref()
.map(|m| matches!(m, MnemonicState::Stored { .. }))
.unwrap_or(false)
{
return ReadyToConnect::NoMnemonicStored;
}
if self.account != Some(AccountState::Active) {
Expand All @@ -265,14 +282,14 @@ impl AccountStateSummary {
fn is_ready(&self) -> Option<ReadyToConnect> {
match self.mnemonic {
Some(MnemonicState::NotStored) => return Some(ReadyToConnect::NoMnemonicStored),
Some(MnemonicState::Stored) => {}
Some(MnemonicState::Stored { .. }) => {}
None => return None,
}
match self.account {
Some(AccountState::NotRegistered) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Inactive) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::DeleteMe) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Active) => {}
Some(AccountState::Inactive { .. }) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::DeleteMe { .. }) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Active { .. }) => {}
None => return None,
}
match self.subscription {
Expand Down Expand Up @@ -318,9 +335,9 @@ fn debug_or_unknown(state: Option<&impl fmt::Debug>) -> String {
.unwrap_or_else(|| "Unknown".to_string())
}

impl From<NymVpnAccountStatusResponse> for AccountState {
fn from(status: NymVpnAccountStatusResponse) -> Self {
match status {
impl From<NymVpnAccountResponse> for AccountState {
fn from(account: NymVpnAccountResponse) -> Self {
match account.status {
NymVpnAccountStatusResponse::Active => AccountState::Active,
NymVpnAccountStatusResponse::Inactive => AccountState::Inactive,
NymVpnAccountStatusResponse::DeleteMe => AccountState::DeleteMe,
Expand Down
13 changes: 13 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use std::{path::PathBuf, str::FromStr, sync::Arc, time::Duration};

use nym_vpn_account_controller::{AccountCommand, ReadyToConnect, SharedAccountState};
use nym_vpn_api_client::types::VpnApiAccount;
use nym_vpn_store::{keys::KeyStore, mnemonic::MnemonicStorage};
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
use tokio_util::sync::CancellationToken;
Expand Down Expand Up @@ -184,6 +185,18 @@ pub(super) async fn is_account_mnemonic_stored(path: &str) -> Result<bool, VpnEr
})
}

pub(super) async fn get_account_id(path: &str) -> Result<String, VpnError> {
let storage = setup_account_storage(path)?;
storage
.load_mnemonic()
.await
.map(VpnApiAccount::from)
.map(|account| account.id())
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

pub(super) async fn remove_account_mnemonic(path: &str) -> Result<bool, VpnError> {
// TODO: remove the mnemonic by sending a command to the account controller instead of directly
// interacting with the storage.
Expand Down
58 changes: 56 additions & 2 deletions nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ use crate::{
TunnelStateMachine, TunnelType,
},
uniffi_custom_impls::{
AccountStateSummary, BandwidthStatus, ConnectionStatus, EntryPoint, ExitPoint,
GatewayMinPerformance, GatewayType, Location, NetworkEnvironment, TunStatus, UserAgent,
AccountLinks, AccountStateSummary, BandwidthStatus, ConnectionStatus, EntryPoint,
ExitPoint, GatewayMinPerformance, GatewayType, Location, NetworkEnvironment, SystemMessage,
TunStatus, UserAgent,
},
};

Expand Down Expand Up @@ -148,6 +149,59 @@ async fn fetch_environment(network_name: &str) -> Result<NetworkEnvironment, Vpn
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn fetchSystemMessages(network_name: &str) -> Result<Vec<SystemMessage>, VpnError> {
RUNTIME.block_on(fetch_system_messages(network_name))
}

async fn fetch_system_messages(network_name: &str) -> Result<Vec<SystemMessage>, VpnError> {
nym_vpn_network_config::Network::fetch(network_name)
.map(|network| {
network
.nym_vpn_network
.system_messages
.into_current_iter()
.map(SystemMessage::from)
.collect()
})
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn fetchAccountLinks(
account_store_path: &str,
network_name: &str,
locale: &str,
) -> Result<AccountLinks, VpnError> {
RUNTIME.block_on(fetch_account_links(
account_store_path,
network_name,
locale,
))
}

async fn fetch_account_links(
path: &str,
network_name: &str,
locale: &str,
) -> Result<AccountLinks, VpnError> {
let account_id = account::get_account_id(path).await?;
nym_vpn_network_config::Network::fetch(network_name)
.and_then(|network| {
network
.nym_vpn_network
.try_into_parsed_links(locale, &account_id)
})
.map(AccountLinks::from)
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn storeAccountMnemonic(mnemonic: String, path: String) -> Result<(), VpnError> {
Expand Down
79 changes: 75 additions & 4 deletions nym-vpn-core/crates/nym-vpn-lib/src/uniffi_custom_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only

use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
str::FromStr,
Expand Down Expand Up @@ -258,13 +259,15 @@ impl UniffiCustomTypeConverter for OffsetDateTime {
pub struct NetworkEnvironment {
pub nym_network: NymNetworkDetails,
pub nym_vpn_network: NymVpnNetwork,
pub feature_flags: Option<FeatureFlags>,
}

impl From<nym_vpn_network_config::Network> for NetworkEnvironment {
fn from(network: nym_vpn_network_config::Network) -> Self {
NetworkEnvironment {
nym_network: network.nym_network.network.into(),
nym_vpn_network: network.nym_vpn_network.into(),
feature_flags: network.feature_flags.map(FeatureFlags::from),
}
}
}
Expand Down Expand Up @@ -375,6 +378,38 @@ impl From<nym_vpn_network_config::NymVpnNetwork> for NymVpnNetwork {
}
}

#[derive(uniffi::Record)]
pub struct FeatureFlags {
pub flags: HashMap<String, FlagValue>,
}

#[derive(uniffi::Enum)]
pub enum FlagValue {
Value(String),
Group(HashMap<String, String>),
}

impl From<nym_vpn_network_config::FeatureFlags> for FeatureFlags {
fn from(value: nym_vpn_network_config::FeatureFlags) -> Self {
FeatureFlags {
flags: value
.flags
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}

impl From<nym_vpn_network_config::feature_flags::FlagValue> for FlagValue {
fn from(value: nym_vpn_network_config::feature_flags::FlagValue) -> Self {
match value {
nym_vpn_network_config::feature_flags::FlagValue::Value(v) => FlagValue::Value(v),
nym_vpn_network_config::feature_flags::FlagValue::Group(g) => FlagValue::Group(g),
}
}
}

#[derive(uniffi::Record)]
pub struct Location {
pub two_letter_iso_country_code: String,
Expand Down Expand Up @@ -656,7 +691,7 @@ impl From<nym_vpn_account_controller::shared_state::MnemonicState> for MnemonicS
nym_vpn_account_controller::shared_state::MnemonicState::NotStored => {
MnemonicState::NotStored
}
nym_vpn_account_controller::shared_state::MnemonicState::Stored => {
nym_vpn_account_controller::shared_state::MnemonicState::Stored { .. } => {
MnemonicState::Stored
}
}
Expand All @@ -677,11 +712,13 @@ impl From<nym_vpn_account_controller::shared_state::AccountState> for AccountSta
nym_vpn_account_controller::shared_state::AccountState::NotRegistered => {
AccountState::NotRegistered
}
nym_vpn_account_controller::shared_state::AccountState::Inactive => {
nym_vpn_account_controller::shared_state::AccountState::Inactive { .. } => {
AccountState::Inactive
}
nym_vpn_account_controller::shared_state::AccountState::Active => AccountState::Active,
nym_vpn_account_controller::shared_state::AccountState::DeleteMe => {
nym_vpn_account_controller::shared_state::AccountState::Active { .. } => {
AccountState::Active
}
nym_vpn_account_controller::shared_state::AccountState::DeleteMe { .. } => {
AccountState::DeleteMe
}
}
Expand Down Expand Up @@ -739,3 +776,37 @@ impl From<nym_vpn_account_controller::shared_state::DeviceState> for DeviceState
}
}
}

#[derive(uniffi::Record, Clone, PartialEq)]
pub struct SystemMessage {
pub name: String,
pub message: String,
pub properties: HashMap<String, String>,
}

impl From<nym_vpn_network_config::SystemMessage> for SystemMessage {
fn from(value: nym_vpn_network_config::SystemMessage) -> Self {
SystemMessage {
name: value.name,
message: value.message,
properties: value.properties.into_inner(),
}
}
}

#[derive(uniffi::Record, Clone, PartialEq)]
pub struct AccountLinks {
pub sign_up: String,
pub sign_in: String,
pub account: String,
}

impl From<nym_vpn_network_config::ParsedAccountLinks> for AccountLinks {
fn from(value: nym_vpn_network_config::ParsedAccountLinks) -> Self {
AccountLinks {
sign_up: value.sign_up.to_string(),
sign_in: value.sign_in.to_string(),
account: value.account.to_string(),
}
}
}
2 changes: 2 additions & 0 deletions nym-vpn-core/crates/nym-vpn-network-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ nym-config.workspace = true
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
time = { workspace = true, features = ["serde-human-readable"] }
tokio = { workspace = true, features = ["time", "macros"] }
tokio-util.workspace = true
tracing.workspace = true
url = { workspace = true, features = ["serde"] }
futures-util = "0.3"

[build-dependencies]
serde_json.workspace = true
3 changes: 3 additions & 0 deletions nym-vpn-core/crates/nym-vpn-network-config/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ fn default_mainnet_discovery() {
network_name: "{}".to_string(),
nym_api_url: "{}".parse().expect("Failed to parse NYM API URL"),
nym_vpn_api_url: "{}".parse().expect("Failed to parse NYM VPN API URL"),
account_management: Default::default(),
feature_flags: Default::default(),
system_messages: Default::default(),
}}
}}
}}
Expand Down
Loading
Loading