mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-11 06:17:08 +00:00
core: add fields to chat relay profiles; remove unique name requirement; update relay profile in relay address link data (#6743)
* core: add fields to chat relay profiles * wip * wip * fix * fix * fix * enable tests * schema * api --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
@@ -1802,7 +1802,6 @@ enum UserServersError: Decodable {
|
||||
case storageMissing(protocol: ServerProtocol, user: UserRef?)
|
||||
case proxyMissing(protocol: ServerProtocol, user: UserRef?)
|
||||
case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String)
|
||||
case duplicateChatRelayName(duplicateChatRelay: String)
|
||||
case duplicateChatRelayAddress(duplicateChatRelay: String, duplicateAddress: String)
|
||||
|
||||
var globalError: String? {
|
||||
|
||||
@@ -422,7 +422,7 @@ struct AddChannelView: View {
|
||||
}
|
||||
|
||||
func relayDisplayName(_ relay: GroupRelay) -> String {
|
||||
if !relay.userChatRelay.name.isEmpty { return relay.userChatRelay.name }
|
||||
if !relay.userChatRelay.displayName.isEmpty { return relay.userChatRelay.displayName }
|
||||
if let domain = relay.userChatRelay.domains.first { return domain }
|
||||
if let link = relay.relayLink { return hostFromRelayLink(link) }
|
||||
return "relay \(relay.groupRelayId)"
|
||||
|
||||
@@ -57,11 +57,11 @@ func addChatRelay(
|
||||
_ serverWarnings: Binding<[UserServersWarning]>? = nil,
|
||||
_ dismiss: DismissAction
|
||||
) {
|
||||
let nameEmpty = relay.name.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
let nameEmpty = relay.displayName.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
let addressEmpty = relay.address.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
if nameEmpty && addressEmpty {
|
||||
dismiss()
|
||||
} else if !validRelayName(relay.name) {
|
||||
} else if !validRelayName(relay.displayName) {
|
||||
dismiss()
|
||||
showAlert(
|
||||
NSLocalizedString("Invalid relay name!", comment: "alert title"),
|
||||
@@ -97,7 +97,7 @@ struct ChatRelayView: View {
|
||||
@State private var testFailure: RelayTestFailure?
|
||||
|
||||
var body: some View {
|
||||
let validName = validRelayName(relayToEdit.name)
|
||||
let validName = validRelayName(relayToEdit.displayName)
|
||||
let validAddress = validRelayAddress(relayToEdit.address)
|
||||
ZStack {
|
||||
if relay.preset {
|
||||
@@ -137,7 +137,7 @@ struct ChatRelayView: View {
|
||||
.onChange(of: relayToEdit.address) { _ in
|
||||
if relayToEdit.address == relay.address {
|
||||
relayToEdit.tested = relay.tested
|
||||
relayToEdit.name = relay.name
|
||||
relayToEdit.displayName = relay.displayName
|
||||
} else {
|
||||
relayToEdit.tested = nil
|
||||
}
|
||||
@@ -150,7 +150,7 @@ struct ChatRelayView: View {
|
||||
if !validName {
|
||||
Spacer()
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
.onTapGesture { showInvalidRelayNameAlert($relayToEdit.name) }
|
||||
.onTapGesture { showInvalidRelayNameAlert($relayToEdit.displayName) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ struct ChatRelayView: View {
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
Section(header: Text("Preset relay name").foregroundColor(theme.colors.secondary)) {
|
||||
Text(relayToEdit.name)
|
||||
Text(relayToEdit.displayName)
|
||||
}
|
||||
useRelaySection()
|
||||
}
|
||||
@@ -190,7 +190,7 @@ struct ChatRelayView: View {
|
||||
}
|
||||
}
|
||||
Section {
|
||||
TextField("Enter relay name…", text: $relayToEdit.name)
|
||||
TextField("Enter relay name…", text: $relayToEdit.displayName)
|
||||
.autocorrectionDisabled(true)
|
||||
.disabled(relayToEdit.tested == true)
|
||||
} header: {
|
||||
@@ -243,7 +243,6 @@ struct ChatRelayViewLink: View {
|
||||
@Binding var serverErrors: [UserServersError]
|
||||
@Binding var serverWarnings: [UserServersWarning]
|
||||
@Binding var relay: UserChatRelay
|
||||
var duplicateRelayNames: Set<String>
|
||||
var duplicateRelayAddresses: Set<String>
|
||||
var backLabel: LocalizedStringKey
|
||||
@Binding var selectedServer: String?
|
||||
@@ -264,7 +263,7 @@ struct ChatRelayViewLink: View {
|
||||
} label: {
|
||||
HStack {
|
||||
Group {
|
||||
if duplicateRelayNames.contains(relay.name) || duplicateRelayAddresses.contains(relay.address) {
|
||||
if duplicateRelayAddresses.contains(relay.address) {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
} else if !relay.enabled {
|
||||
Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary)
|
||||
@@ -275,7 +274,7 @@ struct ChatRelayViewLink: View {
|
||||
.frame(width: 16, alignment: .center)
|
||||
.padding(.trailing, 4)
|
||||
|
||||
let displayName = !relay.name.isEmpty ? relay.name : relay.domains.first ?? relay.address
|
||||
let displayName = !relay.displayName.isEmpty ? relay.displayName : relay.domains.first ?? relay.address
|
||||
let v = Text(displayName).lineLimit(1)
|
||||
if relay.enabled {
|
||||
v
|
||||
@@ -302,7 +301,7 @@ struct NewChatRelayView: View {
|
||||
@State private var testFailure: RelayTestFailure?
|
||||
|
||||
var body: some View {
|
||||
let validName = validRelayName(relayToEdit.name)
|
||||
let validName = validRelayName(relayToEdit.displayName)
|
||||
let validAddress = validRelayAddress(relayToEdit.address)
|
||||
ZStack {
|
||||
List {
|
||||
@@ -326,7 +325,7 @@ struct NewChatRelayView: View {
|
||||
}
|
||||
}
|
||||
Section {
|
||||
TextField("Enter relay name…", text: $relayToEdit.name)
|
||||
TextField("Enter relay name…", text: $relayToEdit.displayName)
|
||||
.autocorrectionDisabled(true)
|
||||
.disabled(relayToEdit.tested == true)
|
||||
} header: {
|
||||
@@ -335,7 +334,7 @@ struct NewChatRelayView: View {
|
||||
if !validName {
|
||||
Spacer()
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
.onTapGesture { showInvalidRelayNameAlert($relayToEdit.name) }
|
||||
.onTapGesture { showInvalidRelayNameAlert($relayToEdit.displayName) }
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
@@ -392,7 +391,7 @@ func testRelayConnection(relay: Binding<UserChatRelay>) async -> RelayTestFailur
|
||||
await MainActor.run {
|
||||
relay.wrappedValue.tested = true
|
||||
if let relayProfile {
|
||||
relay.wrappedValue.name = relayProfile.name
|
||||
relay.wrappedValue.displayName = relayProfile.displayName
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -483,13 +483,6 @@ func findDuplicateHosts(_ serverErrors: [UserServersError]) -> Set<String> {
|
||||
return Set(duplicateHostsList)
|
||||
}
|
||||
|
||||
func findDuplicateRelayNames(_ serverErrors: [UserServersError]) -> Set<String> {
|
||||
Set(serverErrors.compactMap { err in
|
||||
if case let .duplicateChatRelayName(duplicateChatRelay) = err { return duplicateChatRelay }
|
||||
else { return nil }
|
||||
})
|
||||
}
|
||||
|
||||
func findDuplicateRelayAddresses(_ serverErrors: [UserServersError]) -> Set<String> {
|
||||
Set(serverErrors.compactMap { err in
|
||||
if case let .duplicateChatRelayAddress(_, duplicateAddress) = err { return duplicateAddress }
|
||||
|
||||
@@ -42,7 +42,6 @@ struct OperatorView: View {
|
||||
|
||||
private func operatorView() -> some View {
|
||||
let duplicateHosts = findDuplicateHosts(serverErrors)
|
||||
let duplicateRelayNames = findDuplicateRelayNames(serverErrors)
|
||||
let duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors)
|
||||
return VStack {
|
||||
List {
|
||||
@@ -83,7 +82,6 @@ struct OperatorView: View {
|
||||
serverErrors: $serverErrors,
|
||||
serverWarnings: $serverWarnings,
|
||||
relay: relay,
|
||||
duplicateRelayNames: duplicateRelayNames,
|
||||
duplicateRelayAddresses: duplicateRelayAddresses,
|
||||
backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers",
|
||||
selectedServer: $selectedServer
|
||||
|
||||
@@ -43,7 +43,6 @@ struct YourServersView: View {
|
||||
|
||||
private func yourServersView() -> some View {
|
||||
let duplicateHosts = findDuplicateHosts(serverErrors)
|
||||
let duplicateRelayNames = findDuplicateRelayNames(serverErrors)
|
||||
let duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors)
|
||||
return List {
|
||||
if !userServers[operatorIndex].chatRelays.filter({ !$0.deleted }).isEmpty {
|
||||
@@ -55,7 +54,6 @@ struct YourServersView: View {
|
||||
serverErrors: $serverErrors,
|
||||
serverWarnings: $serverWarnings,
|
||||
relay: relay,
|
||||
duplicateRelayNames: duplicateRelayNames,
|
||||
duplicateRelayAddresses: duplicateRelayAddresses,
|
||||
backLabel: "Your servers",
|
||||
selectedServer: $selectedServer
|
||||
@@ -434,7 +432,7 @@ struct TestServersButton: View {
|
||||
for i in 0..<chatRelays.count {
|
||||
if chatRelays[i].enabled && !chatRelays[i].deleted {
|
||||
if let f = await testRelayConnection(relay: $chatRelays[i]) {
|
||||
let name = !chatRelays[i].name.isEmpty ? chatRelays[i].name : chatRelays[i].domains.first ?? chatRelays[i].address
|
||||
let name = !chatRelays[i].displayName.isEmpty ? chatRelays[i].displayName : chatRelays[i].domains.first ?? chatRelays[i].address
|
||||
fs[name] = f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2563,7 +2563,10 @@ public enum RelayStatus: String, Decodable, Equatable, Hashable {
|
||||
}
|
||||
|
||||
public struct RelayProfile: Codable, Equatable, Hashable {
|
||||
public var name: String
|
||||
public var displayName: String
|
||||
public var fullName: String
|
||||
public var shortDescr: String?
|
||||
public var image: String?
|
||||
}
|
||||
|
||||
public struct UserChatRelay: Identifiable, Codable, Equatable, Hashable {
|
||||
@@ -2577,15 +2580,15 @@ public struct UserChatRelay: Identifiable, Codable, Equatable, Hashable {
|
||||
public var deleted: Bool
|
||||
public var createdAt = Date()
|
||||
|
||||
public var name: String {
|
||||
get { relayProfile.name }
|
||||
set { relayProfile.name = newValue }
|
||||
public var displayName: String {
|
||||
get { relayProfile.displayName }
|
||||
set { relayProfile.displayName = newValue }
|
||||
}
|
||||
|
||||
public init(chatRelayId: Int64? = nil, address: String, name: String, domains: [String], preset: Bool, tested: Bool? = nil, enabled: Bool, deleted: Bool, createdAt: Date = Date()) {
|
||||
self.chatRelayId = chatRelayId
|
||||
self.address = address
|
||||
self.relayProfile = RelayProfile(name: name)
|
||||
self.relayProfile = RelayProfile(displayName: name, fullName: "", shortDescr: nil, image: nil)
|
||||
self.domains = domains
|
||||
self.preset = preset
|
||||
self.tested = tested
|
||||
|
||||
+6
-3
@@ -2279,7 +2279,10 @@ enum class RelayStatus {
|
||||
|
||||
@Serializable
|
||||
data class RelayProfile(
|
||||
val name: String
|
||||
val displayName: String,
|
||||
val fullName: String,
|
||||
val shortDescr: String? = null,
|
||||
val image: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -2297,9 +2300,9 @@ data class UserChatRelay(
|
||||
private val createdAt: Date = Date()
|
||||
val id: String get() = "$address $createdAt"
|
||||
|
||||
val name: String get() = relayProfile.name
|
||||
val displayName: String get() = relayProfile.displayName
|
||||
|
||||
fun copyWithName(name: String): UserChatRelay = copy(relayProfile = RelayProfile(name = name))
|
||||
fun copyWithName(name: String): UserChatRelay = copy(relayProfile = relayProfile.copy(displayName = name))
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
-2
@@ -4473,7 +4473,6 @@ sealed class UserServersError {
|
||||
@Serializable @SerialName("storageMissing") data class StorageMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError()
|
||||
@Serializable @SerialName("proxyMissing") data class ProxyMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError()
|
||||
@Serializable @SerialName("duplicateServer") data class DuplicateServer(val protocol: ServerProtocol, val duplicateServer: String, val duplicateHost: String): UserServersError()
|
||||
@Serializable @SerialName("duplicateChatRelayName") data class DuplicateChatRelayName(val duplicateChatRelay: String): UserServersError()
|
||||
@Serializable @SerialName("duplicateChatRelayAddress") data class DuplicateChatRelayAddress(val duplicateChatRelay: String, val duplicateAddress: String): UserServersError()
|
||||
|
||||
val globalError: String?
|
||||
@@ -4489,7 +4488,6 @@ sealed class UserServersError {
|
||||
is StorageMissing -> this.protocol
|
||||
is ProxyMissing -> this.protocol
|
||||
is DuplicateServer -> this.protocol
|
||||
is DuplicateChatRelayName -> null
|
||||
is DuplicateChatRelayAddress -> null
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -539,7 +539,7 @@ private fun LinkStepView(
|
||||
}
|
||||
|
||||
fun relayDisplayName(relay: GroupRelay): String {
|
||||
if (relay.userChatRelay.name.isNotEmpty()) return relay.userChatRelay.name
|
||||
if (relay.userChatRelay.displayName.isNotEmpty()) return relay.userChatRelay.displayName
|
||||
relay.userChatRelay.domains.firstOrNull()?.let { return it }
|
||||
relay.relayLink?.let { return hostFromRelayLink(it) }
|
||||
return "relay ${relay.groupRelayId}"
|
||||
|
||||
+10
-11
@@ -72,11 +72,11 @@ fun addChatRelay(
|
||||
rhId: Long?,
|
||||
close: () -> Unit
|
||||
) {
|
||||
val nameEmpty = relay.name.trim().isEmpty()
|
||||
val nameEmpty = relay.displayName.trim().isEmpty()
|
||||
val addressEmpty = relay.address.trim().isEmpty()
|
||||
if (nameEmpty && addressEmpty) {
|
||||
close()
|
||||
} else if (!validRelayName(relay.name)) {
|
||||
} else if (!validRelayName(relay.displayName)) {
|
||||
close()
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_relay_name),
|
||||
@@ -131,7 +131,7 @@ fun ChatRelayView(
|
||||
|
||||
ModalView(
|
||||
close = {
|
||||
val validName = validRelayName(relayToEdit.value.name)
|
||||
val validName = validRelayName(relayToEdit.value.displayName)
|
||||
val validAddress = validRelayAddress(relayToEdit.value.address)
|
||||
if (validName && validAddress) {
|
||||
onUpdate(relayToEdit.value)
|
||||
@@ -194,7 +194,7 @@ private fun PresetRelay(relay: MutableState<UserChatRelay>, testing: MutableStat
|
||||
SectionDividerSpaced()
|
||||
SectionView(stringResource(MR.strings.preset_relay_name).uppercase()) {
|
||||
SectionItemView {
|
||||
Text(relay.value.name)
|
||||
Text(relay.value.displayName)
|
||||
}
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
@@ -207,7 +207,7 @@ private fun CustomRelay(
|
||||
onDelete: (() -> Unit)?,
|
||||
testing: MutableState<Boolean>
|
||||
) {
|
||||
val relayName = remember { mutableStateOf(relay.value.name) }
|
||||
val relayName = remember { mutableStateOf(relay.value.displayName) }
|
||||
val relayAddress = remember { mutableStateOf(relay.value.address) }
|
||||
val validName = remember { derivedStateOf { validRelayName(relayName.value) } }
|
||||
val validAddress = remember { derivedStateOf { validRelayAddress(relayAddress.value) } }
|
||||
@@ -218,7 +218,7 @@ private fun CustomRelay(
|
||||
.collect { relay.value = relay.value.copyWithName(it) }
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { relay.value.name }
|
||||
snapshotFlow { relay.value.displayName }
|
||||
.distinctUntilChanged()
|
||||
.collect { relayName.value = it }
|
||||
}
|
||||
@@ -329,20 +329,19 @@ private fun UseRelaySection(
|
||||
@Composable
|
||||
fun ChatRelayViewLink(
|
||||
relay: UserChatRelay,
|
||||
duplicateRelayNames: Set<String>,
|
||||
duplicateRelayAddresses: Set<String>,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
SectionItemView(onClick) {
|
||||
Box(Modifier.width(16.dp)) {
|
||||
when {
|
||||
relay.name in duplicateRelayNames || relay.address in duplicateRelayAddresses -> InvalidServer()
|
||||
relay.address in duplicateRelayAddresses -> InvalidServer()
|
||||
!relay.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary)
|
||||
else -> ShowRelayTestStatus(relay)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
val displayName = relay.name.ifEmpty { relay.domains.firstOrNull() ?: relay.address }
|
||||
val displayName = relay.displayName.ifEmpty { relay.domains.firstOrNull() ?: relay.address }
|
||||
if (relay.enabled) {
|
||||
Text(displayName, color = MaterialTheme.colors.onBackground, maxLines = 1)
|
||||
} else {
|
||||
@@ -362,7 +361,7 @@ fun ModalData.NewChatRelayView(
|
||||
val relayToEdit = remember {
|
||||
mutableStateOf(
|
||||
UserChatRelay(
|
||||
chatRelayId = null, address = "", relayProfile = RelayProfile(name = ""), domains = emptyList(),
|
||||
chatRelayId = null, address = "", relayProfile = RelayProfile(displayName = "", fullName = ""), domains = emptyList(),
|
||||
preset = false, tested = null, enabled = true, deleted = false
|
||||
)
|
||||
)
|
||||
@@ -406,7 +405,7 @@ suspend fun testRelayConnection(relay: MutableState<UserChatRelay>): RelayTestFa
|
||||
testFailure
|
||||
} else {
|
||||
relay.value = relay.value.copy(tested = true).let {
|
||||
if (relayProfile != null) it.copyWithName(relayProfile.name) else it
|
||||
if (relayProfile != null) it.copyWithName(relayProfile.displayName) else it
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
-3
@@ -990,9 +990,6 @@ fun findDuplicateHosts(serverErrors: List<UserServersError>): Set<String> {
|
||||
return duplicateHostsList.toSet()
|
||||
}
|
||||
|
||||
fun findDuplicateRelayNames(serverErrors: List<UserServersError>): Set<String> =
|
||||
serverErrors.mapNotNull { (it as? UserServersError.DuplicateChatRelayName)?.duplicateChatRelay }.toSet()
|
||||
|
||||
fun findDuplicateRelayAddresses(serverErrors: List<UserServersError>): Set<String> =
|
||||
serverErrors.mapNotNull { (it as? UserServersError.DuplicateChatRelayAddress)?.duplicateAddress }.toSet()
|
||||
|
||||
|
||||
+1
-2
@@ -236,13 +236,12 @@ fun OperatorViewLayout(
|
||||
|
||||
if (operator.enabled) {
|
||||
if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) {
|
||||
val duplicateRelayNames = findDuplicateRelayNames(serverErrors.value)
|
||||
val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value)
|
||||
SectionDividerSpaced()
|
||||
SectionView(generalGetString(MR.strings.chat_relays).uppercase()) {
|
||||
userServers.value[operatorIndex].chatRelays.forEachIndexed { index, relay ->
|
||||
if (!relay.deleted) {
|
||||
ChatRelayViewLink(relay, duplicateRelayNames, duplicateRelayAddresses) {
|
||||
ChatRelayViewLink(relay, duplicateRelayAddresses) {
|
||||
navigateToChatRelayView(userServers, serverErrors, serverWarnings, operatorIndex, index, relay, rhId)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -85,12 +85,11 @@ fun YourServersViewLayout(
|
||||
|
||||
Column {
|
||||
if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) {
|
||||
val duplicateRelayNames = findDuplicateRelayNames(serverErrors.value)
|
||||
val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value)
|
||||
SectionView(generalGetString(MR.strings.chat_relays).uppercase()) {
|
||||
userServers.value[operatorIndex].chatRelays.forEachIndexed { i, relay ->
|
||||
if (relay.deleted) return@forEachIndexed
|
||||
ChatRelayViewLink(relay, duplicateRelayNames, duplicateRelayAddresses) {
|
||||
ChatRelayViewLink(relay, duplicateRelayAddresses) {
|
||||
navigateToChatRelayView(userServers, serverErrors, serverWarnings, operatorIndex, i, relay, rhId)
|
||||
}
|
||||
}
|
||||
@@ -433,7 +432,7 @@ private suspend fun runRelaysTest(relays: List<UserChatRelay>, onUpdated: (List<
|
||||
updatedRelays.add(index, relayState.value)
|
||||
onUpdated(updatedRelays.toList())
|
||||
if (f != null) {
|
||||
val name = relayState.value.name.ifEmpty { relayState.value.domains.firstOrNull() ?: relayState.value.address }
|
||||
val name = relayState.value.displayName.ifEmpty { relayState.value.domains.firstOrNull() ?: relayState.value.address }
|
||||
fs[name] = f
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -3184,7 +3184,10 @@ MsgBadSignature:
|
||||
## RelayProfile
|
||||
|
||||
**Record type**:
|
||||
- name: string
|
||||
- displayName: string
|
||||
- fullName: string
|
||||
- shortDescr: string?
|
||||
- image: string?
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -3599,7 +3599,10 @@ export namespace RcvGroupEvent {
|
||||
}
|
||||
|
||||
export interface RelayProfile {
|
||||
name: string
|
||||
displayName: string
|
||||
fullName: string
|
||||
shortDescr?: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
export enum RelayStatus {
|
||||
|
||||
@@ -1544,7 +1544,7 @@ processChatCommand vr nm = \case
|
||||
processChatCommand vr nm $ APISetUserServers userId $ L.map (updatedRelays relays') userServers
|
||||
where
|
||||
aUserRelay :: CLINewRelay -> AUserChatRelay
|
||||
aUserRelay CLINewRelay {address, name} = AUCR SDBNew $ newChatRelay name [""] address
|
||||
aUserRelay CLINewRelay {address, name} = AUCR SDBNew $ newChatRelay (relayProfileFromName name) [""] address
|
||||
APIGetServerOperators -> CRServerOperatorConditions <$> withFastStore getServerOperators
|
||||
APISetServerOperators operators -> do
|
||||
as <- asks randomAgentServers
|
||||
@@ -2202,7 +2202,7 @@ processChatCommand vr nm = \case
|
||||
CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user)
|
||||
ListContacts -> withUser $ \User {userId} ->
|
||||
processChatCommand vr nm $ APIListContacts userId
|
||||
APICreateMyAddress userId -> withUserId userId $ \user@User {profile = LocalProfile {displayName}, userChatRelay} -> do
|
||||
APICreateMyAddress userId -> withUserId userId $ \user@User {userChatRelay} -> do
|
||||
withFastStore' (\db -> runExceptT $ getUserAddress db user) >>= \case
|
||||
Left SEUserContactLinkNotFound -> pure ()
|
||||
Left e -> throwError $ ChatErrorStore e
|
||||
@@ -2210,7 +2210,7 @@ processChatCommand vr nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
-- TODO [relays] relay: add identity, key to link data?
|
||||
let userData
|
||||
| isTrue userChatRelay = encodeShortLinkData $ RelayAddressLinkData {relayProfile = RelayProfile {name = displayName}}
|
||||
| isTrue userChatRelay = relayShortLinkData (userProfileDirect user Nothing Nothing True)
|
||||
| otherwise = contactShortLinkData (userProfileDirect user Nothing Nothing True) Nothing
|
||||
userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
-- TODO [certs rcv]
|
||||
@@ -3583,11 +3583,13 @@ processChatCommand vr nm = \case
|
||||
fmap $ \SndMessage {msgId, msgBody} ->
|
||||
(conn, MsgFlags {notification = hasNotification XInfo_}, (vrValue msgBody, [msgId]))
|
||||
setMyAddressData :: User -> UserContactLink -> CM UserContactLink
|
||||
setMyAddressData user ucl@UserContactLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_, addressSettings} = do
|
||||
setMyAddressData user@User {userChatRelay} ucl@UserContactLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_, addressSettings} = do
|
||||
conn <- withFastStore $ \db -> getUserAddressConnection db vr user
|
||||
let shortLinkProfile = userProfileDirect user Nothing Nothing True
|
||||
-- TODO [short links] do not save address to server if data did not change, spinners, error handling
|
||||
userData = contactShortLinkData shortLinkProfile $ Just addressSettings
|
||||
userData
|
||||
| isTrue userChatRelay = relayShortLinkData shortLinkProfile
|
||||
| otherwise = contactShortLinkData shortLinkProfile $ Just addressSettings
|
||||
userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
sLnk <- shortenShortLink' =<< withAgent (\a -> setConnShortLink a nm (aConnId conn) SCMContact userLinkData Nothing)
|
||||
withFastStore' $ \db -> setUserContactLinkShortLink db userContactLinkId sLnk
|
||||
@@ -4063,6 +4065,9 @@ processChatCommand vr nm = \case
|
||||
business = maybe False businessAddress settings
|
||||
contactData = ContactShortLinkData p msg business
|
||||
in encodeShortLinkData contactData
|
||||
relayShortLinkData :: Profile -> UserLinkData
|
||||
relayShortLinkData Profile {displayName, fullName, shortDescr, image} =
|
||||
encodeShortLinkData $ RelayAddressLinkData {relayProfile = RelayProfile {displayName, fullName, shortDescr, image}}
|
||||
updatePCCShortLinkData :: PendingContactConnection -> Profile -> CM (Maybe ShortLinkInvitation)
|
||||
updatePCCShortLinkData conn@PendingContactConnection {connLinkInv} profile =
|
||||
forM (connShortLink =<< connLinkInv) $ \_ -> do
|
||||
|
||||
@@ -331,17 +331,17 @@ newUserServer_ :: Bool -> Bool -> ProtoServerWithAuth p -> NewUserServer p
|
||||
newUserServer_ preset enabled server =
|
||||
UserServer {serverId = DBNewEntity, server, preset, tested = Nothing, enabled, deleted = False}
|
||||
|
||||
presetChatRelay :: Bool -> Text -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
presetChatRelay :: Bool -> RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
presetChatRelay = newChatRelay_ True
|
||||
{-# INLINE presetChatRelay #-}
|
||||
|
||||
newChatRelay :: Text -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
newChatRelay :: RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
newChatRelay = newChatRelay_ False True
|
||||
{-# INLINE newChatRelay #-}
|
||||
|
||||
newChatRelay_ :: Bool -> Bool -> Text -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
newChatRelay_ preset enabled name domains !address =
|
||||
UserChatRelay {chatRelayId = DBNewEntity, address, relayProfile = RelayProfile {name}, domains, preset, tested = Nothing, enabled, deleted = False}
|
||||
newChatRelay_ :: Bool -> Bool -> RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay
|
||||
newChatRelay_ preset enabled relayProfile domains !address =
|
||||
UserChatRelay {chatRelayId = DBNewEntity, address, relayProfile, domains, preset, tested = Nothing, enabled, deleted = False}
|
||||
|
||||
-- This function should be used inside DB transaction to update conditions in the database
|
||||
-- it evaluates to (current conditions, and conditions to add)
|
||||
@@ -507,7 +507,6 @@ data UserServersError
|
||||
| USEStorageMissing {protocol :: AProtocolType, user :: Maybe User}
|
||||
| USEProxyMissing {protocol :: AProtocolType, user :: Maybe User}
|
||||
| USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: Text, duplicateHost :: TransportHost}
|
||||
| USEDuplicateChatRelayName {duplicateChatRelay :: Text}
|
||||
| USEDuplicateChatRelayAddress {duplicateChatRelay :: Text, duplicateAddress :: ShortLinkContact}
|
||||
deriving (Show)
|
||||
|
||||
@@ -544,11 +543,8 @@ validateUserServers curr others = (currUserErrs <> concatMap otherUserErrs other
|
||||
chatRelayErrs uss = concatMap duplicateErrs_ cRelays
|
||||
where
|
||||
cRelays = filter (\(AUCR _ UserChatRelay {deleted}) -> not deleted) $ userChatRelays uss
|
||||
duplicateErrs_ (AUCR _ UserChatRelay {relayProfile = RelayProfile {name}, address}) =
|
||||
[USEDuplicateChatRelayName name | name `elem` duplicateNames]
|
||||
<> [USEDuplicateChatRelayAddress name address | address `elem` duplicateAddresses]
|
||||
duplicateNames = snd $ foldl' addDuplicate (S.empty, S.empty) allNames
|
||||
allNames = map (\(AUCR _ UserChatRelay {relayProfile = RelayProfile {name}}) -> name) cRelays
|
||||
duplicateErrs_ (AUCR _ UserChatRelay {relayProfile = RelayProfile {displayName}, address}) =
|
||||
[USEDuplicateChatRelayAddress displayName address | address `elem` duplicateAddresses]
|
||||
duplicateAddresses = snd $ foldl' addAddress ([], []) allAddresses
|
||||
allAddresses = map (\(AUCR _ UserChatRelay {address}) -> address) cRelays
|
||||
addAddress :: ([ShortLinkContact], [ShortLinkContact]) -> ShortLinkContact -> ([ShortLinkContact], [ShortLinkContact])
|
||||
|
||||
@@ -8,6 +8,7 @@ module Simplex.Chat.Operators.Presets where
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Protocol (relayProfileFromName)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles)
|
||||
import Simplex.Messaging.Agent.Store.Entity
|
||||
import Simplex.Messaging.Encoding.String
|
||||
@@ -91,9 +92,9 @@ disabledSimplexChatSMPServers =
|
||||
-- TODO [relays] real chat relays
|
||||
simplexChatRelays :: [NewUserChatRelay]
|
||||
simplexChatRelays =
|
||||
[ presetChatRelay True "chat_relay_1" ["simplex.im"] (either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"),
|
||||
presetChatRelay True "chat_relay_2" ["simplex.im"] (either error id $ strDecode "https://smp222.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"),
|
||||
presetChatRelay True "chat_relay_3" ["simplex.im"] (either error id $ strDecode "https://smp333.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")
|
||||
[ presetChatRelay True (relayProfileFromName "chat_relay_1") ["simplex.im"] (either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"),
|
||||
presetChatRelay True (relayProfileFromName "chat_relay_2") ["simplex.im"] (either error id $ strDecode "https://smp222.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"),
|
||||
presetChatRelay True (relayProfileFromName "chat_relay_3") ["simplex.im"] (either error id $ strDecode "https://smp333.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")
|
||||
]
|
||||
|
||||
fluxSMPServers :: [NewUserServer 'PSMP]
|
||||
|
||||
@@ -1455,11 +1455,22 @@ data RelayShortLinkData = RelayShortLinkData
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''RelayShortLinkData)
|
||||
|
||||
data RelayProfile = RelayProfile {name :: ContactName}
|
||||
data RelayProfile = RelayProfile
|
||||
{ displayName :: ContactName,
|
||||
fullName :: Text,
|
||||
shortDescr :: Maybe Text,
|
||||
image :: Maybe ImageData
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''RelayProfile)
|
||||
|
||||
toRelayProfile :: (ContactName, Text, Maybe Text, Maybe ImageData) -> RelayProfile
|
||||
toRelayProfile (displayName, fullName, shortDescr, image) = RelayProfile {displayName, fullName, shortDescr, image}
|
||||
|
||||
relayProfileFromName :: ContactName -> RelayProfile
|
||||
relayProfileFromName displayName = RelayProfile {displayName, fullName = "", shortDescr = Nothing, image = Nothing}
|
||||
|
||||
data RelayAddressLinkData = RelayAddressLinkData {relayProfile :: RelayProfile}
|
||||
deriving (Show)
|
||||
|
||||
|
||||
@@ -1329,21 +1329,21 @@ groupRelayQuery :: Query
|
||||
groupRelayQuery =
|
||||
[sql|
|
||||
SELECT gr.group_relay_id, gr.group_member_id,
|
||||
cr.chat_relay_id, cr.address, cr.name, cr.domains, cr.preset, cr.tested, cr.enabled, cr.deleted,
|
||||
cr.chat_relay_id, cr.address, cr.display_name, cr.full_name, cr.short_descr, cr.image, cr.domains, cr.preset, cr.tested, cr.enabled, cr.deleted,
|
||||
gr.relay_status, gr.relay_link
|
||||
FROM group_relays gr
|
||||
JOIN chat_relays cr ON cr.chat_relay_id = gr.chat_relay_id
|
||||
|]
|
||||
|
||||
toGroupRelay :: (Int64, GroupMemberId, DBEntityId, ShortLinkContact, Text, Text, BoolInt, Maybe BoolInt, BoolInt, BoolInt, RelayStatus, Maybe ShortLinkContact) -> GroupRelay
|
||||
toGroupRelay (groupRelayId, groupMemberId, chatRelayId, address, name, domains, BI preset, tested, BI enabled, BI deleted, relayStatus, relayLink) =
|
||||
let userChatRelay = UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted}
|
||||
toGroupRelay :: (Int64, GroupMemberId, DBEntityId, ShortLinkContact, Text, Text, Maybe Text, Maybe ImageData, Text, BoolInt) :. (Maybe BoolInt, BoolInt, BoolInt, RelayStatus, Maybe ShortLinkContact) -> GroupRelay
|
||||
toGroupRelay ((groupRelayId, groupMemberId, chatRelayId, address, displayName, fullName, shortDescr, image, domains, BI preset) :. (tested, BI enabled, BI deleted, relayStatus, relayLink)) =
|
||||
let userChatRelay = UserChatRelay {chatRelayId, address, relayProfile = toRelayProfile (displayName, fullName, shortDescr, image), domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted}
|
||||
in GroupRelay {groupRelayId, groupMemberId, userChatRelay, relayStatus, relayLink}
|
||||
|
||||
createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupMember
|
||||
createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {relayProfile = RelayProfile {name}} = do
|
||||
createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {relayProfile = RelayProfile {displayName}} = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let relayProfile = profileFromName name
|
||||
let relayProfile = profileFromName displayName
|
||||
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs
|
||||
groupMemberId <- createWithRandomId' db gVar $ \memId -> runExceptT $ do
|
||||
indexInGroup <- getUpdateNextIndexInGroup_ db groupId
|
||||
|
||||
@@ -13,7 +13,10 @@ m20260222_chat_relays =
|
||||
CREATE TABLE chat_relays(
|
||||
chat_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
address BYTEA NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
full_name TEXT NOT NULL DEFAULT '',
|
||||
short_descr TEXT,
|
||||
image TEXT,
|
||||
domains TEXT NOT NULL,
|
||||
preset SMALLINT NOT NULL DEFAULT 0,
|
||||
tested SMALLINT,
|
||||
@@ -25,7 +28,6 @@ CREATE TABLE chat_relays(
|
||||
);
|
||||
CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address);
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
|
||||
|
||||
ALTER TABLE users ADD COLUMN is_user_chat_relay SMALLINT NOT NULL DEFAULT 0;
|
||||
|
||||
@@ -111,7 +113,6 @@ DROP TABLE group_relays;
|
||||
|
||||
DROP INDEX idx_chat_relays_user_id;
|
||||
DROP INDEX idx_chat_relays_user_id_address;
|
||||
DROP INDEX idx_chat_relays_user_id_name;
|
||||
DROP TABLE chat_relays;
|
||||
|
||||
ALTER TABLE group_members
|
||||
|
||||
@@ -363,7 +363,10 @@ ALTER TABLE test_chat_schema.chat_items ALTER COLUMN chat_item_id ADD GENERATED
|
||||
CREATE TABLE test_chat_schema.chat_relays (
|
||||
chat_relay_id bigint NOT NULL,
|
||||
address bytea NOT NULL,
|
||||
name text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
full_name text DEFAULT ''::text NOT NULL,
|
||||
short_descr text,
|
||||
image text,
|
||||
domains text NOT NULL,
|
||||
preset smallint DEFAULT 0 NOT NULL,
|
||||
tested smallint,
|
||||
@@ -2027,10 +2030,6 @@ CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON test_chat_schema.chat_rel
|
||||
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON test_chat_schema.chat_relays USING btree (user_id, name);
|
||||
|
||||
|
||||
|
||||
CREATE INDEX idx_chat_tags_chats_chat_tag_id ON test_chat_schema.chat_tags_chats USING btree (chat_tag_id);
|
||||
|
||||
|
||||
|
||||
@@ -626,15 +626,15 @@ getChatRelays db User {userId} =
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_relay_id, address, name, domains, preset, tested, enabled
|
||||
SELECT chat_relay_id, address, display_name, full_name, short_descr, image, domains, preset, tested, enabled
|
||||
FROM chat_relays
|
||||
WHERE user_id = ? AND deleted = 0
|
||||
|]
|
||||
(Only userId)
|
||||
|
||||
toChatRelay :: (DBEntityId, ShortLinkContact, Text, Text, BoolInt, Maybe BoolInt, BoolInt) -> UserChatRelay
|
||||
toChatRelay (chatRelayId, address, name, domains, BI preset, tested, BI enabled) =
|
||||
UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted = False}
|
||||
toChatRelay :: (DBEntityId, ShortLinkContact, Text, Text, Maybe Text, Maybe ImageData, Text, BoolInt, Maybe BoolInt, BoolInt) -> UserChatRelay
|
||||
toChatRelay (chatRelayId, address, displayName, fullName, shortDescr, image, domains, BI preset, tested, BI enabled) =
|
||||
UserChatRelay {chatRelayId, address, relayProfile = toRelayProfile (displayName, fullName, shortDescr, image), domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted = False}
|
||||
|
||||
getChatRelayById :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO UserChatRelay
|
||||
getChatRelayById db User {userId} relayId =
|
||||
@@ -642,38 +642,38 @@ getChatRelayById db User {userId} relayId =
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_relay_id, address, name, domains, preset, tested, enabled
|
||||
SELECT chat_relay_id, address, display_name, full_name, short_descr, image, domains, preset, tested, enabled
|
||||
FROM chat_relays
|
||||
WHERE user_id = ? AND chat_relay_id = ? AND deleted = 0
|
||||
|]
|
||||
(userId, relayId)
|
||||
|
||||
insertChatRelay :: DB.Connection -> User -> UTCTime -> NewUserChatRelay -> IO UserChatRelay
|
||||
insertChatRelay db User {userId} ts relay@UserChatRelay {address, relayProfile = RelayProfile {name}, domains, preset, tested, enabled} = do
|
||||
insertChatRelay db User {userId} ts relay@UserChatRelay {address, relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} = do
|
||||
crId <-
|
||||
fromOnly . head
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO chat_relays
|
||||
(address, name, domains, preset, tested, enabled, user_id, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)
|
||||
(address, display_name, full_name, short_descr, image, domains, preset, tested, enabled, user_id, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
RETURNING chat_relay_id
|
||||
|]
|
||||
(address, name, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, userId, ts, ts)
|
||||
((address, displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, userId) :. (ts, ts))
|
||||
pure (relay :: NewUserChatRelay) {chatRelayId = DBEntityId crId}
|
||||
|
||||
updateChatRelay :: DB.Connection -> UTCTime -> UserChatRelay -> IO ()
|
||||
updateChatRelay db ts UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains, preset, tested, enabled} =
|
||||
updateChatRelay db ts UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_relays
|
||||
SET address = ?, name = ?, domains = ?,
|
||||
SET address = ?, display_name = ?, full_name = ?, short_descr = ?, image = ?, domains = ?,
|
||||
preset = ?, tested = ?, enabled = ?, updated_at = ?
|
||||
WHERE chat_relay_id = ?
|
||||
|]
|
||||
(address, name, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, chatRelayId)
|
||||
((address, displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts) :. Only chatRelayId)
|
||||
|
||||
getServerOperators :: DB.Connection -> ExceptT StoreError IO ServerOperatorConditions
|
||||
getServerOperators db = do
|
||||
@@ -948,15 +948,15 @@ setUserServers' db user@User {userId} ts UpdatedUserOperatorServers {operator, s
|
||||
| otherwise -> Just relay <$ updateChatRelay db ts relay
|
||||
-- Un-delete soft-deleted relay, updating name and settings but keeping the address unchanged.
|
||||
undeleteRelay :: Int64 -> NewUserChatRelay -> IO ()
|
||||
undeleteRelay existingId UserChatRelay {relayProfile = RelayProfile {name = nm}, domains, preset, tested, enabled} =
|
||||
undeleteRelay existingId UserChatRelay {relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} =
|
||||
DB.execute db
|
||||
[sql|
|
||||
UPDATE chat_relays
|
||||
SET name = ?, domains = ?,
|
||||
SET display_name = ?, full_name = ?, short_descr = ?, image = ?, domains = ?,
|
||||
preset = ?, tested = ?, enabled = ?, deleted = 0, updated_at = ?
|
||||
WHERE chat_relay_id = ?
|
||||
|]
|
||||
(nm, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, existingId)
|
||||
(displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, existingId)
|
||||
|
||||
createCall :: DB.Connection -> User -> Call -> UTCTime -> IO ()
|
||||
createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do
|
||||
|
||||
@@ -24,7 +24,10 @@ m20260222_chat_relays =
|
||||
CREATE TABLE chat_relays(
|
||||
chat_relay_id INTEGER PRIMARY KEY,
|
||||
address BLOB NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
full_name TEXT NOT NULL DEFAULT '',
|
||||
short_descr TEXT,
|
||||
image TEXT,
|
||||
domains TEXT NOT NULL,
|
||||
preset INTEGER NOT NULL DEFAULT 0,
|
||||
tested INTEGER,
|
||||
@@ -36,7 +39,6 @@ CREATE TABLE chat_relays(
|
||||
) STRICT;
|
||||
CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address);
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
|
||||
|
||||
ALTER TABLE users ADD COLUMN is_user_chat_relay INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
@@ -116,7 +118,6 @@ DROP TABLE group_relays;
|
||||
|
||||
DROP INDEX idx_chat_relays_user_id;
|
||||
DROP INDEX idx_chat_relays_user_id_address;
|
||||
DROP INDEX idx_chat_relays_user_id_name;
|
||||
DROP TABLE chat_relays;
|
||||
|
||||
ALTER TABLE group_members DROP COLUMN relay_link;
|
||||
|
||||
@@ -750,7 +750,10 @@ CREATE TABLE connections_sync(
|
||||
CREATE TABLE chat_relays(
|
||||
chat_relay_id INTEGER PRIMARY KEY,
|
||||
address BLOB NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
full_name TEXT NOT NULL DEFAULT '',
|
||||
short_descr TEXT,
|
||||
image TEXT,
|
||||
domains TEXT NOT NULL,
|
||||
preset INTEGER NOT NULL DEFAULT 0,
|
||||
tested INTEGER,
|
||||
@@ -1270,7 +1273,6 @@ CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(
|
||||
user_id,
|
||||
address
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
|
||||
CREATE INDEX idx_group_relays_group_id ON group_relays(group_id);
|
||||
CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(
|
||||
group_member_id
|
||||
|
||||
@@ -1579,7 +1579,7 @@ viewUserServers UserOperatorServers {operator, smpServers, xftpServers, chatRela
|
||||
[" Chat relays"] <> map (plain . (" " <>) . viewChatRelay) cRelays
|
||||
| otherwise = []
|
||||
where
|
||||
viewChatRelay UserChatRelay {relayProfile = RelayProfile {name}, address, preset, tested, enabled} = name <> relayAddress <> relayInfo
|
||||
viewChatRelay UserChatRelay {relayProfile = RelayProfile {displayName, fullName, shortDescr}, address, preset, tested, enabled} = displayName <> optionalFullName displayName fullName shortDescr <> relayAddress <> relayInfo
|
||||
where
|
||||
relayAddress = ": " <> safeDecodeUtf8 (strEncode address)
|
||||
relayInfo = if null relayInfo_ then "" else parens $ T.intercalate ", " relayInfo_
|
||||
@@ -1619,7 +1619,7 @@ viewRelayTestResult relayProfile_ = \case
|
||||
Just RelayTestFailure {rtfStep, rtfError} ->
|
||||
["relay test failed at " <> plain (show rtfStep) <> ", error: " <> plain (show rtfError)]
|
||||
Nothing -> case relayProfile_ of
|
||||
Just RelayProfile {name} -> ["relay test passed, profile: " <> plain (T.unpack name)]
|
||||
Just RelayProfile {displayName, fullName, shortDescr} -> ["relay test passed, profile: " <> ttyFullName displayName fullName shortDescr]
|
||||
Nothing -> ["relay test passed"]
|
||||
|
||||
viewServerOperators :: [ServerOperator] -> Maybe UsageConditionsAction -> [StyledString]
|
||||
|
||||
@@ -3,6 +3,7 @@ module ChatTests.ChatRelays where
|
||||
import ChatClient
|
||||
import ChatTests.DBUtils
|
||||
import ChatTests.Utils
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Test.Hspec hiding (it)
|
||||
|
||||
chatRelayTests :: SpecWith TestParams
|
||||
@@ -12,6 +13,7 @@ chatRelayTests = do
|
||||
it "re-add soft-deleted relay by same address" testReAddRelaySameAddress
|
||||
it "re-add soft-deleted relay by same name" testReAddRelaySameName
|
||||
it "test chat relay" testChatRelayTest
|
||||
it "relay profile updated in address" testRelayProfileUpdateInAddress
|
||||
|
||||
testGetSetChatRelays :: HasCallStack => TestParams -> IO ()
|
||||
testGetSetChatRelays ps =
|
||||
@@ -131,7 +133,7 @@ testChatRelayTest ps =
|
||||
|
||||
-- Scenario 1: Happy path - test relay address succeeds
|
||||
alice ##> ("/relay test " <> bobSLink)
|
||||
alice <## "relay test passed, profile: bob"
|
||||
alice <## "relay test passed, profile: bob (Bob)"
|
||||
|
||||
-- Scenario 2: Non-relay address - cath is not a relay user,
|
||||
-- her address has ContactShortLinkData, not RelayAddressLinkData
|
||||
@@ -145,6 +147,24 @@ testChatRelayTest ps =
|
||||
alice ##> ("/relay test " <> bobSLink)
|
||||
alice <##. "relay test failed at RTSGetLink, error: "
|
||||
|
||||
testRelayProfileUpdateInAddress :: HasCallStack => TestParams -> IO ()
|
||||
testRelayProfileUpdateInAddress ps =
|
||||
withNewTestChat ps "alice" aliceProfile $ \alice ->
|
||||
withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> do
|
||||
bob ##> "/ad"
|
||||
(bobSLink, _cLink) <- getContactLinks bob True
|
||||
|
||||
alice ##> ("/relay test " <> bobSLink)
|
||||
alice <## "relay test passed, profile: bob (Bob)"
|
||||
|
||||
bob ##> "/p bob2 Bob relay"
|
||||
bob <## "user profile is changed to bob2 (Bob relay) (your 0 contacts are notified)"
|
||||
|
||||
threadDelay 100000
|
||||
|
||||
alice ##> ("/relay test " <> bobSLink)
|
||||
alice <## "relay test passed, profile: bob2 (Bob relay)"
|
||||
|
||||
-- Create a public group with relay=1, wait for relay to join
|
||||
createChannelWithRelay :: HasCallStack => String -> TestCC -> TestCC -> IO ()
|
||||
createChannelWithRelay gName owner relay = do
|
||||
|
||||
+9
-14
@@ -20,7 +20,7 @@ import Simplex.Chat
|
||||
import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..))
|
||||
import Simplex.Chat.Operators
|
||||
import Simplex.Chat.Operators.Presets
|
||||
import Simplex.Chat.Protocol (RelayProfile (..))
|
||||
import Simplex.Chat.Protocol (RelayProfile (..), relayProfileFromName)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles)
|
||||
@@ -52,13 +52,8 @@ validateServersTest = describe "validate user servers" $ do
|
||||
)
|
||||
it "should warn without chat relays" $
|
||||
validateUserServers [invalidNoChatRelays] [] `shouldBe` ([], [USWNoChatRelays Nothing])
|
||||
it "should fail with duplicate chat relay name" $ do
|
||||
validateUserServers [invalidDuplicateChatRelayName] []
|
||||
`shouldBe` ( [ USEDuplicateChatRelayName "chat_relay_1",
|
||||
USEDuplicateChatRelayName "chat_relay_1"
|
||||
],
|
||||
[]
|
||||
)
|
||||
it "should allow duplicate chat relay name" $
|
||||
validateUserServers [duplicateChatRelayName] [] `shouldBe` ([], [])
|
||||
it "should fail with duplicate chat relay address" $ do
|
||||
validateUserServers [invalidDuplicateChatRelayAddress] []
|
||||
`shouldBe` ( [ USEDuplicateChatRelayAddress "chat_relay_1" duplicateAddr,
|
||||
@@ -98,7 +93,7 @@ updatedServersTest = describe "validate user servers" $ do
|
||||
( ops'',
|
||||
saveSrvs $ take 3 simplexChatSMPServers <> [newUserServer "smp://abcd@smp.example.im"],
|
||||
saveSrvs $ map (presetServer True) $ L.take 3 defaultXFTPServers,
|
||||
saveRelays $ take 2 simplexChatRelays <> [newChatRelay "custom_relay" ["example.im"] customRelayAddr]
|
||||
saveRelays $ take 2 simplexChatRelays <> [newChatRelay (relayProfileFromName "custom_relay") ["example.im"] customRelayAddr]
|
||||
)
|
||||
[op1, op2, op3] <- pure $ map updatedUserServers uss
|
||||
[p1, p2] <- pure operators -- presets
|
||||
@@ -123,7 +118,7 @@ updatedServersTest = describe "validate user servers" $ do
|
||||
map chatRelayAddress presetRelays `shouldBe` map relayAddr' (chatRelays' op)
|
||||
srvHost' (AUS _ s) = srvHost s
|
||||
relayAddr' (AUCR _ r) = chatRelayAddress r
|
||||
relayName' (AUCR _ UserChatRelay {relayProfile = RelayProfile {name}}) = name
|
||||
relayName' (AUCR _ UserChatRelay {relayProfile = RelayProfile {displayName}}) = displayName
|
||||
PresetServers {operators} = presetServers defaultChatConfig
|
||||
customRelayAddr = either error id $ strDecode "https://relay.example.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"
|
||||
|
||||
@@ -172,16 +167,16 @@ invalidDuplicateSrv =
|
||||
invalidNoChatRelays :: UpdatedUserOperatorServers
|
||||
invalidNoChatRelays = (valid :: UpdatedUserOperatorServers) {chatRelays = []}
|
||||
|
||||
invalidDuplicateChatRelayName :: UpdatedUserOperatorServers
|
||||
invalidDuplicateChatRelayName =
|
||||
duplicateChatRelayName :: UpdatedUserOperatorServers
|
||||
duplicateChatRelayName =
|
||||
(valid :: UpdatedUserOperatorServers)
|
||||
{ chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True "chat_relay_1" ["simplex.im"] (either error id $ strDecode "https://smp444.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")]
|
||||
{ chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True (relayProfileFromName "chat_relay_1") ["simplex.im"] (either error id $ strDecode "https://smp444.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")]
|
||||
}
|
||||
|
||||
invalidDuplicateChatRelayAddress :: UpdatedUserOperatorServers
|
||||
invalidDuplicateChatRelayAddress =
|
||||
(valid :: UpdatedUserOperatorServers)
|
||||
{ chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True "chat_relay_4" ["simplex.im"] duplicateAddr]
|
||||
{ chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True (relayProfileFromName "chat_relay_4") ["simplex.im"] duplicateAddr]
|
||||
}
|
||||
|
||||
duplicateAddr :: ShortLinkContact
|
||||
|
||||
Reference in New Issue
Block a user