android: UI to access servers via SOCKS proxy (#840)

* android: UI to access servers via SOCKS proxy

* UI to connect via socks

* add server hosts to contact info

* ios: types for network/info commands
This commit is contained in:
Evgeny Poberezkin
2022-07-26 07:29:48 +01:00
committed by GitHub
parent e7f3dc3f41
commit 6069108bb9
9 changed files with 278 additions and 6 deletions

View File

@@ -332,6 +332,42 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
}
}
suspend fun getNetworkConfig(): NetCfg? {
val r = sendCmd(CC.APIGetNetworkConfig())
if (r is CR.NetworkConfig) return r.networkConfig
Log.e(TAG, "getNetworkConfig bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun setNetworkConfig(cfg: NetCfg): Boolean {
val r = sendCmd(CC.APISetNetworkConfig(cfg))
return when (r) {
is CR.CmdOk -> true
else -> {
Log.e(TAG, "setNetworkConfig bad response: ${r.responseType} ${r.details}")
AlertManager.shared.showAlertMsg(
generalGetString(R.string.error_setting_network_config),
"${r.responseType}: ${r.details}"
)
false
}
}
}
suspend fun apiContactInfo(contactId: Long): ConnectionStats? {
val r = sendCmd(CC.APIContactInfo(contactId))
if (r is CR.ContactInfo) return r.connectionStats
Log.e(TAG, "apiContactInfo bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiGroupMemberInfo(groupId: Long, groupMemberId: Long): ConnectionStats? {
val r = sendCmd(CC.APIGroupMemberInfo(groupId, groupMemberId))
if (r is CR.GroupMemberInfo) return r.connectionStats_
Log.e(TAG, "apiGroupMemberInfo bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiAddContact(): String? {
val r = sendCmd(CC.AddContact())
if (r is CR.Invitation) return r.connReqInvitation
@@ -941,8 +977,12 @@ sealed class CC {
class ApiRemoveMember(val groupId: Long, val memberId: Long): CC()
class ApiLeaveGroup(val groupId: Long): CC()
class ApiListMembers(val groupId: Long): CC()
class GetUserSMPServers(): CC()
class GetUserSMPServers: CC()
class SetUserSMPServers(val smpServers: List<String>): CC()
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
class APIGetNetworkConfig: CC()
class APIContactInfo(val contactId: Long): CC()
class APIGroupMemberInfo(val groupId: Long, val groupMemberId: Long): CC()
class AddContact: CC()
class Connect(val connReq: String): CC()
class ApiDeleteChat(val type: ChatType, val id: Long): CC()
@@ -987,6 +1027,10 @@ sealed class CC {
is ApiListMembers -> "/_members #$groupId"
is GetUserSMPServers -> "/smp_servers"
is SetUserSMPServers -> "/smp_servers ${smpServersStr(smpServers)}"
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
is APIGetNetworkConfig -> "/network"
is APIContactInfo -> "/_info @$contactId"
is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId"
is AddContact -> "/connect"
is Connect -> "/connect $connReq"
is ApiDeleteChat -> "/_delete ${chatRef(type, id)}"
@@ -1032,6 +1076,10 @@ sealed class CC {
is ApiListMembers -> "apiListMembers"
is GetUserSMPServers -> "getUserSMPServers"
is SetUserSMPServers -> "setUserSMPServers"
is APISetNetworkConfig -> "/apiSetNetworkConfig"
is APIGetNetworkConfig -> "/apiGetNetworkConfig"
is APIContactInfo -> "apiContactInfo"
is APIGroupMemberInfo -> "apiGroupMemberInfo"
is AddContact -> "addContact"
is Connect -> "connect"
is ApiDeleteChat -> "apiDeleteChat"
@@ -1069,6 +1117,9 @@ class ComposedMessage(val filePath: String?, val quotedItemId: Long?, val msgCon
@Serializable
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
@Serializable
class NetCfg(val socksProxy: String? = null, val tcpTimeout: Int)
val json = Json {
prettyPrint = true
ignoreUnknownKeys = true
@@ -1106,6 +1157,9 @@ sealed class CR {
@Serializable @SerialName("apiChats") class ApiChats(val chats: List<Chat>): CR()
@Serializable @SerialName("apiChat") class ApiChat(val chat: Chat): CR()
@Serializable @SerialName("userSMPServers") class UserSMPServers(val smpServers: List<String>): CR()
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
@Serializable @SerialName("contactInfo") class ContactInfo(val contact: Contact, val connectionStats: ConnectionStats): CR()
@Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats?): CR()
@Serializable @SerialName("invitation") class Invitation(val connReqInvitation: String): CR()
@Serializable @SerialName("sentConfirmation") class SentConfirmation: CR()
@Serializable @SerialName("sentInvitation") class SentInvitation: CR()
@@ -1187,6 +1241,9 @@ sealed class CR {
is ApiChats -> "apiChats"
is ApiChat -> "apiChat"
is UserSMPServers -> "userSMPServers"
is NetworkConfig -> "networkConfig"
is ContactInfo -> "contactInfo"
is GroupMemberInfo -> "groupMemberInfo"
is Invitation -> "invitation"
is SentConfirmation -> "sentConfirmation"
is SentInvitation -> "sentInvitation"
@@ -1266,6 +1323,9 @@ sealed class CR {
is ApiChats -> json.encodeToString(chats)
is ApiChat -> json.encodeToString(chat)
is UserSMPServers -> json.encodeToString(smpServers)
is NetworkConfig -> json.encodeToString(networkConfig)
is ContactInfo -> "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}"
is GroupMemberInfo -> "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}"
is Invitation -> connReqInvitation
is SentConfirmation -> noDetails()
is SentInvitation -> noDetails()
@@ -1367,6 +1427,9 @@ abstract class TerminalItem {
}
}
@Serializable
class ConnectionStats(val rcvServers: List<String>?, val sndServers: List<String>?)
@Serializable
sealed class ChatError {
val string: String get() = when (this) {

View File

@@ -22,12 +22,13 @@ import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun ChatInfoView(chatModel: ChatModel, close: () -> Unit) {
fun ChatInfoView(chatModel: ChatModel, connStats: ConnectionStats?, close: () -> Unit) {
BackHandler(onBack = close)
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
if (chat != null) {
ChatInfoLayout(
chat,
connStats,
close = close,
deleteContact = { deleteChatDialog(chat.chatInfo, chatModel, close) },
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }
@@ -87,6 +88,7 @@ fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel) {
@Composable
fun ChatInfoLayout(
chat: Chat,
connStats: ConnectionStats?,
close: () -> Unit,
deleteContact: () -> Unit,
clearChat: () -> Unit
@@ -135,6 +137,10 @@ fun ChatInfoLayout(
.padding(top = 16.dp)
.padding(horizontal = 16.dp)
)
if (connStats != null) {
SimplexServers("receiving via: ", connStats.rcvServers)
SimplexServers("sending via: ", connStats.sndServers)
}
}
Spacer(Modifier.weight(1F))
@@ -178,6 +184,14 @@ fun ChatInfoLayout(
}
}
@Composable
fun SimplexServers(text: String, servers: List<String>?) {
if (servers != null) {
val info = text + servers.joinToString(separator = ", ") { it.substringAfter("@") }
Text(info, style = MaterialTheme.typography.body2)
}
}
@Composable
fun ServerImage(chat: Chat) {
when (chat.serverInfo.networkStatus) {
@@ -201,6 +215,7 @@ fun PreviewChatInfoLayout() {
chatItems = arrayListOf(),
serverInfo = Chat.ServerInfo(Chat.NetworkStatus.Error("agent BROKER TIMEOUT"))
),
connStats = null,
close = {}, deleteContact = {}, clearChat = {}
)
}

View File

@@ -88,7 +88,16 @@ fun ChatView(chatModel: ChatModel) {
chatModel.chatItems,
useLinkPreviews = useLinkPreviews,
back = { chatModel.chatId.value = null },
info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } },
info = {
withApi {
var connStats: ConnectionStats? = null
val cInfo = chat.chatInfo
if (cInfo is ChatInfo.Direct) {
connStats = chatModel.controller.apiContactInfo(cInfo.apiId)
}
ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, connStats, close) }
}
},
openDirectChat = { contactId ->
val c = chatModel.chats.firstOrNull {
it.chatInfo is ChatInfo.Direct && it.chatInfo.contact.contactId == contactId

View File

@@ -0,0 +1,97 @@
package chat.simplex.app.views.usersettings
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.NetCfg
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@Composable
fun NetworkSettingsView(chatModel: ChatModel, netCfg: NetCfg) {
val useSocksProxy = remember { mutableStateOf(netCfg.socksProxy != null) }
NetworkSettingsLayout(
useSocksProxy,
toggleSocksProxy = { enable ->
if (enable) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_enable_socks),
text = generalGetString(R.string.network_enable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.setNetworkConfig(NetCfg(socksProxy = ":9050", tcpTimeout = 10_000_000))
useSocksProxy.value = true
}
}
)
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_disable_socks),
text = generalGetString(R.string.network_disable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.setNetworkConfig(NetCfg(tcpTimeout = 5_000_000))
useSocksProxy.value = false
}
}
)
}
}
)
}
@Composable fun NetworkSettingsLayout(
useSocksProxy: MutableState<Boolean>,
toggleSocksProxy: (Boolean) -> Unit
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.network_settings_title),
Modifier.padding(start = 16.dp, bottom = 24.dp),
style = MaterialTheme.typography.h1
)
SettingsSectionView(stringResource(R.string.settings_section_title_socks)) {
Row(
Modifier.padding(start = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(stringResource(R.string.network_socks_toggle))
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = useSocksProxy.value,
onCheckedChange = toggleSocksProxy,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewNetworkSettings() {
SimpleXTheme {
NetworkSettingsLayout(
useSocksProxy = remember { mutableStateOf(true) },
toggleSocksProxy = {}
)
}
}

View File

@@ -57,7 +57,20 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
}
} } },
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } }
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } },
showNetworkSettings = {
withApi {
val cfg = chatModel.controller.getNetworkConfig()
if (cfg != null) {
ModalManager.shared.showCustomModal { close ->
ModalView(close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
NetworkSettingsView(chatModel, cfg)
}
}
}
}
}
// showVideoChatPrototype = { ModalManager.shared.showCustomModal { close -> CallViewDebug(close) } },
)
}
@@ -77,6 +90,7 @@ fun SettingsLayout(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
showTerminal: () -> Unit,
showNetworkSettings: () -> Unit
// showVideoChatPrototype: () -> Unit
) {
val uriHandler = LocalUriHandler.current
@@ -115,6 +129,8 @@ fun SettingsLayout(
PrivateNotificationsItem(runServiceInBackground, setRunServiceInBackground, stopped)
divider()
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showModal { SMPServersView(it) }, disabled = stopped)
divider()
SettingsActionItem(Icons.Outlined.SettingsEthernet, stringResource(R.string.network_settings), showNetworkSettings, disabled = stopped)
}
spacer()
@@ -349,6 +365,7 @@ fun PreviewSettingsLayout() {
showSettingsModal = { {} },
showCustomModal = { {} },
showTerminal = {},
showNetworkSettings = {}
// showVideoChatPrototype = {}
)
}

View File

@@ -33,9 +33,10 @@
<string name="description_via_contact_address_link">через ссылку-контакт</string>
<string name="description_via_one_time_link">через одноразовую ссылку</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<!-- SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Ошибка при сохранении SMP серверов</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется.</string>
<string name="error_setting_network_config">Ошибка при сохранении настроек сети</string>
<!-- API Error Responses - SimpleXAPI.kt -->
<string name="contact_already_exists">Существующий контакт</string>
@@ -265,6 +266,13 @@
<string name="enter_one_SMP_server_per_line">Введите SMP серверы, каждый сервер в отдельной строке:</string>
<string name="how_to">Инфо</string>
<string name="save_servers_button">Сохранить</string>
<string name="network_settings">Настройки сети</string>
<string name="network_settings_title">Настройки сети</string>
<string name="network_socks_toggle">Использовать SOCKS прокси (порт 9050)</string>
<string name="network_enable_socks">Использовать SOCKS прокси?</string>
<string name="network_enable_socks_info">Соединяться с серверами через SOCKS прокси через порт 9050? Прокси должен быть запущен до включения этой опции.</string>
<string name="network_disable_socks">Использовать прямое соединение с Интернет?</string>
<string name="network_disable_socks_info">Если вы подтвердите, серверы смогут видеть ваш IP адрес, а провайдер - с какими серверами вы соединяетесь.</string>
<!-- Address Items - UserAddressView.kt -->
<string name="create_address">Создать адрес</string>
@@ -441,6 +449,7 @@
<string name="settings_section_title_device">УСТРОЙСТВО</string>
<string name="settings_section_title_chats">ЧАТЫ</string>
<string name="settings_experimental_features">Экспериментальные функции</string>
<string name="settings_section_title_socks">SOCKS ПРОКСИ</string>
<!-- DatabaseView.kt -->
<string name="your_chat_database">Данные чата</string>

View File

@@ -33,9 +33,10 @@
<string name="description_via_contact_address_link">via contact address link</string>
<string name="description_via_one_time_link">via one-time link</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<!-- SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Error saving SMP servers</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
<string name="error_setting_network_config">Error updating network configuration</string>
<!-- API Error Responses - SimpleXAPI.kt -->
<string name="contact_already_exists">Contact already exists</string>
@@ -271,6 +272,13 @@
<string name="enter_one_SMP_server_per_line">Enter one SMP server per line:</string>
<string name="how_to">How to</string>
<string name="save_servers_button">Save</string>
<string name="network_settings">Network</string>
<string name="network_settings_title">Network settings</string>
<string name="network_socks_toggle">Use SOCKS proxy (port 9050)</string>
<string name="network_enable_socks">Use SOCKS proxy?</string>
<string name="network_enable_socks_info">Access the servers via SOCKS proxy on port 9050? Proxy must be started before enabling this option.</string>
<string name="network_disable_socks">Use direct Internet connection?</string>
<string name="network_disable_socks_info">If you confirm, the messaging servers will be able to see your IP address, and your provider - which servers you are connecting to.</string>
<!-- Address Items - UserAddressView.kt -->
<string name="create_address">Create address</string>
@@ -443,6 +451,7 @@
<string name="settings_section_title_device">DEVICE</string>
<string name="settings_section_title_chats">CHATS</string>
<string name="settings_experimental_features">Experimental features</string>
<string name="settings_section_title_socks">SOCKS PROXY</string>
<!-- DatabaseView.kt -->
<string name="your_chat_database">Your chat database</string>