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:
Evgeny
2026-04-03 13:42:43 +01:00
committed by GitHub
parent 0ca7cdaf1d
commit 8167f7c2ab
29 changed files with 153 additions and 131 deletions
-1
View File
@@ -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
}
}
+8 -5
View File
@@ -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
@@ -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
@@ -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
}
@@ -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}"
@@ -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
}
@@ -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()
@@ -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)
}
}
@@ -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
View File
@@ -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 {
+10 -5
View File
@@ -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
+7 -11
View File
@@ -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])
+4 -3
View File
@@ -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]
+12 -1
View File
@@ -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)
+6 -6
View File
@@ -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);
+15 -15
View File
@@ -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
+2 -2
View File
@@ -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]
+21 -1
View File
@@ -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
View File
@@ -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