core: track network statuses, use in commands/events (#3211)
* core: track network statuses, use in commands/events * ui types, test * remove comment
This commit is contained in:
committed by
GitHub
parent
675fc19745
commit
ab290fb068
@@ -688,41 +688,3 @@ final class Chat: ObservableObject, Identifiable {
|
|||||||
|
|
||||||
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NetworkStatus: Decodable, Equatable {
|
|
||||||
case unknown
|
|
||||||
case connected
|
|
||||||
case disconnected
|
|
||||||
case error(String)
|
|
||||||
|
|
||||||
var statusString: LocalizedStringKey {
|
|
||||||
get {
|
|
||||||
switch self {
|
|
||||||
case .connected: return "connected"
|
|
||||||
case .error: return "error"
|
|
||||||
default: return "connecting"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusExplanation: LocalizedStringKey {
|
|
||||||
get {
|
|
||||||
switch self {
|
|
||||||
case .connected: return "You are connected to the server used to receive messages from this contact."
|
|
||||||
case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))."
|
|
||||||
default: return "Trying to connect to the server used to receive messages from this contact."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageName: String {
|
|
||||||
get {
|
|
||||||
switch self {
|
|
||||||
case .unknown: return "circle.dotted"
|
|
||||||
case .connected: return "circle.fill"
|
|
||||||
case .disconnected: return "ellipsis.circle.fill"
|
|
||||||
case .error: return "exclamationmark.circle.fill"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -944,6 +944,12 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
|
||||||
|
let r = chatSendCmdSync(.apiGetNetworkStatuses)
|
||||||
|
if case let .networkStatuses(_, statuses) = r { return statuses }
|
||||||
|
throw r
|
||||||
|
}
|
||||||
|
|
||||||
func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
|
func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
|
||||||
do {
|
do {
|
||||||
if chat.chatStats.unreadCount > 0 {
|
if chat.chatStats.unreadCount > 0 {
|
||||||
@@ -1348,13 +1354,6 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
|||||||
await updateContactsStatus(contactRefs, status: .connected)
|
await updateContactsStatus(contactRefs, status: .connected)
|
||||||
case let .contactsDisconnected(_, contactRefs):
|
case let .contactsDisconnected(_, contactRefs):
|
||||||
await updateContactsStatus(contactRefs, status: .disconnected)
|
await updateContactsStatus(contactRefs, status: .disconnected)
|
||||||
case let .contactSubError(user, contact, chatError):
|
|
||||||
await MainActor.run {
|
|
||||||
if active(user) {
|
|
||||||
m.updateContact(contact)
|
|
||||||
}
|
|
||||||
processContactSubError(contact, chatError)
|
|
||||||
}
|
|
||||||
case let .contactSubSummary(_, contactSubscriptions):
|
case let .contactSubSummary(_, contactSubscriptions):
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
for sub in contactSubscriptions {
|
for sub in contactSubscriptions {
|
||||||
@@ -1369,6 +1368,18 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .networkStatus(status, connections):
|
||||||
|
await MainActor.run {
|
||||||
|
for cId in connections {
|
||||||
|
m.networkStatuses[cId] = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .networkStatuses(statuses): ()
|
||||||
|
await MainActor.run {
|
||||||
|
for s in statuses {
|
||||||
|
m.networkStatuses[s.agentConnId] = s.networkStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
case let .newChatItem(user, aChatItem):
|
case let .newChatItem(user, aChatItem):
|
||||||
let cInfo = aChatItem.chatInfo
|
let cInfo = aChatItem.chatInfo
|
||||||
let cItem = aChatItem.chatItem
|
let cItem = aChatItem.chatItem
|
||||||
@@ -1649,7 +1660,7 @@ func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
|
|||||||
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
||||||
default: err = String(describing: chatError)
|
default: err = String(describing: chatError)
|
||||||
}
|
}
|
||||||
m.setContactNetworkStatus(contact, .error(err))
|
m.setContactNetworkStatus(contact, .error(connectionError: err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshCallInvitations() throws {
|
func refreshCallInvitations() throws {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ public enum ChatCommand {
|
|||||||
case apiEndCall(contact: Contact)
|
case apiEndCall(contact: Contact)
|
||||||
case apiGetCallInvitations
|
case apiGetCallInvitations
|
||||||
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
||||||
|
case apiGetNetworkStatuses
|
||||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
||||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||||
case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?)
|
case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?)
|
||||||
@@ -241,6 +242,7 @@ public enum ChatCommand {
|
|||||||
case let .apiEndCall(contact): return "/_call end @\(contact.apiId)"
|
case let .apiEndCall(contact): return "/_call end @\(contact.apiId)"
|
||||||
case .apiGetCallInvitations: return "/_call get"
|
case .apiGetCallInvitations: return "/_call get"
|
||||||
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
|
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
|
||||||
|
case .apiGetNetworkStatuses: return "/_network_statuses"
|
||||||
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
||||||
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
||||||
case let .receiveFile(fileId, encrypted, inline):
|
case let .receiveFile(fileId, encrypted, inline):
|
||||||
@@ -356,6 +358,7 @@ public enum ChatCommand {
|
|||||||
case .apiEndCall: return "apiEndCall"
|
case .apiEndCall: return "apiEndCall"
|
||||||
case .apiGetCallInvitations: return "apiGetCallInvitations"
|
case .apiGetCallInvitations: return "apiGetCallInvitations"
|
||||||
case .apiCallStatus: return "apiCallStatus"
|
case .apiCallStatus: return "apiCallStatus"
|
||||||
|
case .apiGetNetworkStatuses: return "apiGetNetworkStatuses"
|
||||||
case .apiChatRead: return "apiChatRead"
|
case .apiChatRead: return "apiChatRead"
|
||||||
case .apiChatUnread: return "apiChatUnread"
|
case .apiChatUnread: return "apiChatUnread"
|
||||||
case .receiveFile: return "receiveFile"
|
case .receiveFile: return "receiveFile"
|
||||||
@@ -480,11 +483,14 @@ public enum ChatResponse: Decodable, Error {
|
|||||||
case acceptingContactRequest(user: UserRef, contact: Contact)
|
case acceptingContactRequest(user: UserRef, contact: Contact)
|
||||||
case contactRequestRejected(user: UserRef)
|
case contactRequestRejected(user: UserRef)
|
||||||
case contactUpdated(user: UserRef, toContact: Contact)
|
case contactUpdated(user: UserRef, toContact: Contact)
|
||||||
|
// TODO remove events below
|
||||||
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
||||||
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
||||||
case contactSubError(user: UserRef, contact: Contact, chatError: ChatError)
|
|
||||||
case contactSubSummary(user: UserRef, contactSubscriptions: [ContactSubStatus])
|
case contactSubSummary(user: UserRef, contactSubscriptions: [ContactSubStatus])
|
||||||
case groupSubscribed(user: UserRef, groupInfo: GroupInfo)
|
// TODO remove events above
|
||||||
|
case networkStatus(networkStatus: NetworkStatus, connections: [String])
|
||||||
|
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||||
|
case groupSubscribed(user: UserRef, groupInfo: GroupRef)
|
||||||
case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError])
|
case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError])
|
||||||
case groupEmpty(user: UserRef, groupInfo: GroupInfo)
|
case groupEmpty(user: UserRef, groupInfo: GroupInfo)
|
||||||
case userContactLinkSubscribed
|
case userContactLinkSubscribed
|
||||||
@@ -620,8 +626,9 @@ public enum ChatResponse: Decodable, Error {
|
|||||||
case .contactUpdated: return "contactUpdated"
|
case .contactUpdated: return "contactUpdated"
|
||||||
case .contactsSubscribed: return "contactsSubscribed"
|
case .contactsSubscribed: return "contactsSubscribed"
|
||||||
case .contactsDisconnected: return "contactsDisconnected"
|
case .contactsDisconnected: return "contactsDisconnected"
|
||||||
case .contactSubError: return "contactSubError"
|
|
||||||
case .contactSubSummary: return "contactSubSummary"
|
case .contactSubSummary: return "contactSubSummary"
|
||||||
|
case .networkStatus: return "networkStatus"
|
||||||
|
case .networkStatuses: return "networkStatuses"
|
||||||
case .groupSubscribed: return "groupSubscribed"
|
case .groupSubscribed: return "groupSubscribed"
|
||||||
case .memberSubErrors: return "memberSubErrors"
|
case .memberSubErrors: return "memberSubErrors"
|
||||||
case .groupEmpty: return "groupEmpty"
|
case .groupEmpty: return "groupEmpty"
|
||||||
@@ -757,8 +764,9 @@ public enum ChatResponse: Decodable, Error {
|
|||||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||||
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||||
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||||
case let .contactSubError(u, contact, chatError): return withUser(u, "contact:\n\(String(describing: contact))\nerror:\n\(String(describing: chatError))")
|
|
||||||
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
||||||
|
case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
|
||||||
|
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||||
case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||||
case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors))
|
case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors))
|
||||||
case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||||
@@ -1181,6 +1189,49 @@ public struct KeepAliveOpts: Codable, Equatable {
|
|||||||
public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4)
|
public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum NetworkStatus: Decodable, Equatable {
|
||||||
|
case unknown
|
||||||
|
case connected
|
||||||
|
case disconnected
|
||||||
|
case error(connectionError: String)
|
||||||
|
|
||||||
|
public var statusString: LocalizedStringKey {
|
||||||
|
get {
|
||||||
|
switch self {
|
||||||
|
case .connected: return "connected"
|
||||||
|
case .error: return "error"
|
||||||
|
default: return "connecting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var statusExplanation: LocalizedStringKey {
|
||||||
|
get {
|
||||||
|
switch self {
|
||||||
|
case .connected: return "You are connected to the server used to receive messages from this contact."
|
||||||
|
case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))."
|
||||||
|
default: return "Trying to connect to the server used to receive messages from this contact."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var imageName: String {
|
||||||
|
get {
|
||||||
|
switch self {
|
||||||
|
case .unknown: return "circle.dotted"
|
||||||
|
case .connected: return "circle.fill"
|
||||||
|
case .disconnected: return "ellipsis.circle.fill"
|
||||||
|
case .error: return "exclamationmark.circle.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ConnNetworkStatus: Decodable {
|
||||||
|
public var agentConnId: String
|
||||||
|
public var networkStatus: NetworkStatus
|
||||||
|
}
|
||||||
|
|
||||||
public struct ChatSettings: Codable {
|
public struct ChatSettings: Codable {
|
||||||
public var enableNtfs: MsgFilter
|
public var enableNtfs: MsgFilter
|
||||||
public var sendRcpts: Bool?
|
public var sendRcpts: Bool?
|
||||||
|
|||||||
@@ -1729,6 +1729,11 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct GroupRef: Decodable {
|
||||||
|
public var groupId: Int64
|
||||||
|
var localDisplayName: GroupName
|
||||||
|
}
|
||||||
|
|
||||||
public struct GroupProfile: Codable, NamedChat {
|
public struct GroupProfile: Codable, NamedChat {
|
||||||
public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
|
public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
|
||||||
self.displayName = displayName
|
self.displayName = displayName
|
||||||
@@ -1871,6 +1876,11 @@ public struct GroupMemberRef: Decodable {
|
|||||||
var profile: Profile
|
var profile: Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct GroupMemberIds: Decodable {
|
||||||
|
var groupMemberId: Int64
|
||||||
|
var groupId: Int64
|
||||||
|
}
|
||||||
|
|
||||||
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
||||||
case observer = "observer"
|
case observer = "observer"
|
||||||
case member = "member"
|
case member = "member"
|
||||||
@@ -1963,7 +1973,7 @@ public enum InvitedBy: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct MemberSubError: Decodable {
|
public struct MemberSubError: Decodable {
|
||||||
var member: GroupMember
|
var member: GroupMemberIds
|
||||||
var memberError: ChatError
|
var memberError: ChatError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -789,16 +789,19 @@ sealed class NetworkStatus {
|
|||||||
val statusExplanation: String get() =
|
val statusExplanation: String get() =
|
||||||
when (this) {
|
when (this) {
|
||||||
is Connected -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
|
is Connected -> generalGetString(MR.strings.connected_to_server_to_receive_messages_from_contact)
|
||||||
is Error -> String.format(generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages_with_error), error)
|
is Error -> String.format(generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages_with_error), connectionError)
|
||||||
else -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
|
else -> generalGetString(MR.strings.trying_to_connect_to_server_to_receive_messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
|
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
|
||||||
@Serializable @SerialName("connected") class Connected: NetworkStatus()
|
@Serializable @SerialName("connected") class Connected: NetworkStatus()
|
||||||
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
|
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
|
||||||
@Serializable @SerialName("error") class Error(val error: String): NetworkStatus()
|
@Serializable @SerialName("error") class Error(val connectionError: String): NetworkStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ConnNetworkStatus(val agentConnId: String, val networkStatus: NetworkStatus)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Contact(
|
data class Contact(
|
||||||
val contactId: Long,
|
val contactId: Long,
|
||||||
@@ -1051,6 +1054,9 @@ data class GroupInfo (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GroupRef(val groupId: Long, val localDisplayName: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GroupProfile (
|
data class GroupProfile (
|
||||||
override val displayName: String,
|
override val displayName: String,
|
||||||
@@ -1159,11 +1165,17 @@ data class GroupMember (
|
|||||||
data class GroupMemberSettings(val showMessages: Boolean) {}
|
data class GroupMemberSettings(val showMessages: Boolean) {}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class GroupMemberRef(
|
data class GroupMemberRef(
|
||||||
val groupMemberId: Long,
|
val groupMemberId: Long,
|
||||||
val profile: Profile
|
val profile: Profile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GroupMemberIds(
|
||||||
|
val groupMemberId: Long,
|
||||||
|
val groupId: Long
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class GroupMemberRole(val memberRole: String) {
|
enum class GroupMemberRole(val memberRole: String) {
|
||||||
@SerialName("observer") Observer("observer"), // order matters in comparisons
|
@SerialName("observer") Observer("observer"), // order matters in comparisons
|
||||||
@@ -1257,7 +1269,7 @@ class LinkPreview (
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class MemberSubError (
|
class MemberSubError (
|
||||||
val member: GroupMember,
|
val member: GroupMemberIds,
|
||||||
val memberError: ChatError
|
val memberError: ChatError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1082,6 +1082,13 @@ object ChatController {
|
|||||||
return r is CR.CmdOk
|
return r is CR.CmdOk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun apiGetNetworkStatuses(): List<ConnNetworkStatus>? {
|
||||||
|
val r = sendCmd(CC.ApiGetNetworkStatuses())
|
||||||
|
if (r is CR.NetworkStatuses) return r.networkStatuses
|
||||||
|
Log.e(TAG, "apiGetNetworkStatuses bad response: ${r.responseType} ${r.details}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun apiChatRead(type: ChatType, id: Long, range: CC.ItemRange): Boolean {
|
suspend fun apiChatRead(type: ChatType, id: Long, range: CC.ItemRange): Boolean {
|
||||||
val r = sendCmd(CC.ApiChatRead(type, id, range))
|
val r = sendCmd(CC.ApiChatRead(type, id, range))
|
||||||
if (r is CR.CmdOk) return true
|
if (r is CR.CmdOk) return true
|
||||||
@@ -1425,12 +1432,6 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
|
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
|
||||||
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
|
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
|
||||||
is CR.ContactSubError -> {
|
|
||||||
if (active(r.user)) {
|
|
||||||
chatModel.updateContact(r.contact)
|
|
||||||
}
|
|
||||||
processContactSubError(r.contact, r.chatError)
|
|
||||||
}
|
|
||||||
is CR.ContactSubSummary -> {
|
is CR.ContactSubSummary -> {
|
||||||
for (sub in r.contactSubscriptions) {
|
for (sub in r.contactSubscriptions) {
|
||||||
if (active(r.user)) {
|
if (active(r.user)) {
|
||||||
@@ -1444,6 +1445,16 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is CR.NetworkStatusResp -> {
|
||||||
|
for (cId in r.connections) {
|
||||||
|
chatModel.networkStatuses[cId] = r.networkStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is CR.NetworkStatuses -> {
|
||||||
|
for (s in r.networkStatuses) {
|
||||||
|
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
is CR.NewChatItem -> {
|
is CR.NewChatItem -> {
|
||||||
val cInfo = r.chatItem.chatInfo
|
val cInfo = r.chatItem.chatInfo
|
||||||
val cItem = r.chatItem.chatItem
|
val cItem = r.chatItem.chatItem
|
||||||
@@ -1915,6 +1926,7 @@ sealed class CC {
|
|||||||
class ApiSendCallExtraInfo(val contact: Contact, val extraInfo: WebRTCExtraInfo): CC()
|
class ApiSendCallExtraInfo(val contact: Contact, val extraInfo: WebRTCExtraInfo): CC()
|
||||||
class ApiEndCall(val contact: Contact): CC()
|
class ApiEndCall(val contact: Contact): CC()
|
||||||
class ApiCallStatus(val contact: Contact, val callStatus: WebRTCCallStatus): CC()
|
class ApiCallStatus(val contact: Contact, val callStatus: WebRTCCallStatus): CC()
|
||||||
|
class ApiGetNetworkStatuses(): CC()
|
||||||
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
|
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
|
||||||
class ApiRejectContact(val contactReqId: Long): CC()
|
class ApiRejectContact(val contactReqId: Long): CC()
|
||||||
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
|
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
|
||||||
@@ -2024,6 +2036,7 @@ sealed class CC {
|
|||||||
is ApiSendCallExtraInfo -> "/_call extra @${contact.apiId} ${json.encodeToString(extraInfo)}"
|
is ApiSendCallExtraInfo -> "/_call extra @${contact.apiId} ${json.encodeToString(extraInfo)}"
|
||||||
is ApiEndCall -> "/_call end @${contact.apiId}"
|
is ApiEndCall -> "/_call end @${contact.apiId}"
|
||||||
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
|
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
|
||||||
|
is ApiGetNetworkStatuses -> "/_network_statuses"
|
||||||
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
|
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
|
||||||
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
|
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
|
||||||
is ReceiveFile -> "/freceive $fileId encrypt=${onOff(encrypted)}" + (if (inline == null) "" else " inline=${onOff(inline)}")
|
is ReceiveFile -> "/freceive $fileId encrypt=${onOff(encrypted)}" + (if (inline == null) "" else " inline=${onOff(inline)}")
|
||||||
@@ -2120,6 +2133,7 @@ sealed class CC {
|
|||||||
is ApiSendCallExtraInfo -> "apiSendCallExtraInfo"
|
is ApiSendCallExtraInfo -> "apiSendCallExtraInfo"
|
||||||
is ApiEndCall -> "apiEndCall"
|
is ApiEndCall -> "apiEndCall"
|
||||||
is ApiCallStatus -> "apiCallStatus"
|
is ApiCallStatus -> "apiCallStatus"
|
||||||
|
is ApiGetNetworkStatuses -> "apiGetNetworkStatuses"
|
||||||
is ApiChatRead -> "apiChatRead"
|
is ApiChatRead -> "apiChatRead"
|
||||||
is ApiChatUnread -> "apiChatUnread"
|
is ApiChatUnread -> "apiChatUnread"
|
||||||
is ReceiveFile -> "receiveFile"
|
is ReceiveFile -> "receiveFile"
|
||||||
@@ -3333,11 +3347,14 @@ sealed class CR {
|
|||||||
@Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val user: UserRef, val contact: Contact): CR()
|
@Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val user: UserRef, val contact: Contact): CR()
|
||||||
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef): CR()
|
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef): CR()
|
||||||
@Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR()
|
@Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR()
|
||||||
|
// TODO remove below
|
||||||
@Serializable @SerialName("contactsSubscribed") class ContactsSubscribed(val server: String, val contactRefs: List<ContactRef>): CR()
|
@Serializable @SerialName("contactsSubscribed") class ContactsSubscribed(val server: String, val contactRefs: List<ContactRef>): CR()
|
||||||
@Serializable @SerialName("contactsDisconnected") class ContactsDisconnected(val server: String, val contactRefs: List<ContactRef>): CR()
|
@Serializable @SerialName("contactsDisconnected") class ContactsDisconnected(val server: String, val contactRefs: List<ContactRef>): CR()
|
||||||
@Serializable @SerialName("contactSubError") class ContactSubError(val user: UserRef, val contact: Contact, val chatError: ChatError): CR()
|
|
||||||
@Serializable @SerialName("contactSubSummary") class ContactSubSummary(val user: UserRef, val contactSubscriptions: List<ContactSubStatus>): CR()
|
@Serializable @SerialName("contactSubSummary") class ContactSubSummary(val user: UserRef, val contactSubscriptions: List<ContactSubStatus>): CR()
|
||||||
@Serializable @SerialName("groupSubscribed") class GroupSubscribed(val user: UserRef, val group: GroupInfo): CR()
|
// TODO remove above
|
||||||
|
@Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List<String>): CR()
|
||||||
|
@Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List<ConnNetworkStatus>): CR()
|
||||||
|
@Serializable @SerialName("groupSubscribed") class GroupSubscribed(val user: UserRef, val group: GroupRef): CR()
|
||||||
@Serializable @SerialName("memberSubErrors") class MemberSubErrors(val user: UserRef, val memberSubErrors: List<MemberSubError>): CR()
|
@Serializable @SerialName("memberSubErrors") class MemberSubErrors(val user: UserRef, val memberSubErrors: List<MemberSubError>): CR()
|
||||||
@Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR()
|
@Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR()
|
||||||
@Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR()
|
@Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR()
|
||||||
@@ -3467,8 +3484,9 @@ sealed class CR {
|
|||||||
is ContactUpdated -> "contactUpdated"
|
is ContactUpdated -> "contactUpdated"
|
||||||
is ContactsSubscribed -> "contactsSubscribed"
|
is ContactsSubscribed -> "contactsSubscribed"
|
||||||
is ContactsDisconnected -> "contactsDisconnected"
|
is ContactsDisconnected -> "contactsDisconnected"
|
||||||
is ContactSubError -> "contactSubError"
|
|
||||||
is ContactSubSummary -> "contactSubSummary"
|
is ContactSubSummary -> "contactSubSummary"
|
||||||
|
is NetworkStatusResp -> "networkStatus"
|
||||||
|
is NetworkStatuses -> "networkStatuses"
|
||||||
is GroupSubscribed -> "groupSubscribed"
|
is GroupSubscribed -> "groupSubscribed"
|
||||||
is MemberSubErrors -> "memberSubErrors"
|
is MemberSubErrors -> "memberSubErrors"
|
||||||
is GroupEmpty -> "groupEmpty"
|
is GroupEmpty -> "groupEmpty"
|
||||||
@@ -3596,8 +3614,9 @@ sealed class CR {
|
|||||||
is ContactUpdated -> withUser(user, json.encodeToString(toContact))
|
is ContactUpdated -> withUser(user, json.encodeToString(toContact))
|
||||||
is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
||||||
is ContactsDisconnected -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
is ContactsDisconnected -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
||||||
is ContactSubError -> withUser(user, "error:\n${chatError.string}\ncontact:\n${json.encodeToString(contact)}")
|
|
||||||
is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions))
|
is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions))
|
||||||
|
is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections"
|
||||||
|
is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses))
|
||||||
is GroupSubscribed -> withUser(user, json.encodeToString(group))
|
is GroupSubscribed -> withUser(user, json.encodeToString(group))
|
||||||
is MemberSubErrors -> withUser(user, json.encodeToString(memberSubErrors))
|
is MemberSubErrors -> withUser(user, json.encodeToString(memberSubErrors))
|
||||||
is GroupEmpty -> withUser(user, json.encodeToString(group))
|
is GroupEmpty -> withUser(user, json.encodeToString(group))
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ defaultChatConfig =
|
|||||||
initialCleanupManagerDelay = 30 * 1000000, -- 30 seconds
|
initialCleanupManagerDelay = 30 * 1000000, -- 30 seconds
|
||||||
cleanupManagerInterval = 30 * 60, -- 30 minutes
|
cleanupManagerInterval = 30 * 60, -- 30 minutes
|
||||||
cleanupManagerStepDelay = 3 * 1000000, -- 3 seconds
|
cleanupManagerStepDelay = 3 * 1000000, -- 3 seconds
|
||||||
ciExpirationInterval = 30 * 60 * 1000000 -- 30 minutes
|
ciExpirationInterval = 30 * 60 * 1000000, -- 30 minutes
|
||||||
|
coreApi = False
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaultSMPServers :: NonEmpty SMPServerWithAuth
|
_defaultSMPServers :: NonEmpty SMPServerWithAuth
|
||||||
@@ -195,6 +196,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
|
|||||||
idsDrg <- newTVarIO =<< liftIO drgNew
|
idsDrg <- newTVarIO =<< liftIO drgNew
|
||||||
inputQ <- newTBQueueIO tbqSize
|
inputQ <- newTBQueueIO tbqSize
|
||||||
outputQ <- newTBQueueIO tbqSize
|
outputQ <- newTBQueueIO tbqSize
|
||||||
|
connNetworkStatuses <- atomically TM.empty
|
||||||
subscriptionMode <- newTVarIO SMSubscribe
|
subscriptionMode <- newTVarIO SMSubscribe
|
||||||
chatLock <- newEmptyTMVarIO
|
chatLock <- newEmptyTMVarIO
|
||||||
sndFiles <- newTVarIO M.empty
|
sndFiles <- newTVarIO M.empty
|
||||||
@@ -221,6 +223,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
|
|||||||
idsDrg,
|
idsDrg,
|
||||||
inputQ,
|
inputQ,
|
||||||
outputQ,
|
outputQ,
|
||||||
|
connNetworkStatuses,
|
||||||
subscriptionMode,
|
subscriptionMode,
|
||||||
chatLock,
|
chatLock,
|
||||||
sndFiles,
|
sndFiles,
|
||||||
@@ -1086,6 +1089,8 @@ processChatCommand = \case
|
|||||||
user <- getUserByContactId db contactId
|
user <- getUserByContactId db contactId
|
||||||
contact <- getContact db user contactId
|
contact <- getContact db user contactId
|
||||||
pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callTs}
|
pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callTs}
|
||||||
|
APIGetNetworkStatuses -> withUser $ \_ ->
|
||||||
|
CRNetworkStatuses Nothing . map (uncurry ConnNetworkStatus) . M.toList <$> chatReadVar connNetworkStatuses
|
||||||
APICallStatus contactId receivedStatus ->
|
APICallStatus contactId receivedStatus ->
|
||||||
withCurrentCall contactId $ \user ct call ->
|
withCurrentCall contactId $ \user ct call ->
|
||||||
updateCallItemStatus user ct call receivedStatus Nothing $> Just call
|
updateCallItemStatus user ct call receivedStatus Nothing $> Just call
|
||||||
@@ -1688,6 +1693,8 @@ processChatCommand = \case
|
|||||||
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode
|
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode
|
||||||
-- [incognito] reuse membership incognito profile
|
-- [incognito] reuse membership incognito profile
|
||||||
ct <- withStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode
|
ct <- withStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode
|
||||||
|
-- TODO not sure it is correct to set connections status here?
|
||||||
|
setContactNetworkStatus ct NSConnected
|
||||||
pure $ CRNewMemberContact user ct g m
|
pure $ CRNewMemberContact user ct g m
|
||||||
_ -> throwChatError CEGroupMemberNotActive
|
_ -> throwChatError CEGroupMemberNotActive
|
||||||
APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do
|
APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do
|
||||||
@@ -2627,6 +2634,7 @@ subscribeUserConnections onlyNeeded agentBatchSubscribe user@User {userId} = do
|
|||||||
rs <- withAgent $ \a -> agentBatchSubscribe a conns
|
rs <- withAgent $ \a -> agentBatchSubscribe a conns
|
||||||
-- send connection events to view
|
-- send connection events to view
|
||||||
contactSubsToView rs cts ce
|
contactSubsToView rs cts ce
|
||||||
|
-- TODO possibly, we could either disable these events or replace with less noisy for API
|
||||||
contactLinkSubsToView rs ucs
|
contactLinkSubsToView rs ucs
|
||||||
groupSubsToView rs gs ms ce
|
groupSubsToView rs gs ms ce
|
||||||
sndFileSubsToView rs sfts
|
sndFileSubsToView rs sfts
|
||||||
@@ -2687,12 +2695,30 @@ subscribeUserConnections onlyNeeded agentBatchSubscribe user@User {userId} = do
|
|||||||
let connIds = map aConnId' pcs
|
let connIds = map aConnId' pcs
|
||||||
pure (connIds, M.fromList $ zip connIds pcs)
|
pure (connIds, M.fromList $ zip connIds pcs)
|
||||||
contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> m ()
|
contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> m ()
|
||||||
contactSubsToView rs cts ce = do
|
contactSubsToView rs cts ce = ifM (asks $ coreApi . config) notifyAPI notifyCLI
|
||||||
toView . CRContactSubSummary user $ map (uncurry ContactSubStatus) cRs
|
|
||||||
when ce $ mapM_ (toView . uncurry (CRContactSubError user)) cErrors
|
|
||||||
where
|
where
|
||||||
cRs = resultsFor rs cts
|
notifyCLI = do
|
||||||
cErrors = sortOn (\(Contact {localDisplayName = n}, _) -> n) $ filterErrors cRs
|
let cRs = resultsFor rs cts
|
||||||
|
cErrors = sortOn (\(Contact {localDisplayName = n}, _) -> n) $ filterErrors cRs
|
||||||
|
toView . CRContactSubSummary user $ map (uncurry ContactSubStatus) cRs
|
||||||
|
when ce $ mapM_ (toView . uncurry (CRContactSubError user)) cErrors
|
||||||
|
notifyAPI = do
|
||||||
|
let statuses = M.foldrWithKey' addStatus [] cts
|
||||||
|
chatModifyVar connNetworkStatuses $ M.union (M.fromList statuses)
|
||||||
|
toView $ CRNetworkStatuses (Just user) $ map (uncurry ConnNetworkStatus) statuses
|
||||||
|
where
|
||||||
|
addStatus :: ConnId -> Contact -> [(AgentConnId, NetworkStatus)] -> [(AgentConnId, NetworkStatus)]
|
||||||
|
addStatus connId ct =
|
||||||
|
let ns = (contactAgentConnId ct, netStatus $ resultErr connId rs)
|
||||||
|
in (ns :)
|
||||||
|
netStatus :: Maybe ChatError -> NetworkStatus
|
||||||
|
netStatus = maybe NSConnected $ NSError . errorNetworkStatus
|
||||||
|
errorNetworkStatus :: ChatError -> String
|
||||||
|
errorNetworkStatus = \case
|
||||||
|
ChatErrorAgent (BROKER _ NETWORK) _ -> "network"
|
||||||
|
ChatErrorAgent (SMP SMP.AUTH) _ -> "contact deleted"
|
||||||
|
e -> show e
|
||||||
|
-- TODO possibly below could be replaced with less noisy events for API
|
||||||
contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> m ()
|
contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> m ()
|
||||||
contactLinkSubsToView rs = toView . CRUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs
|
contactLinkSubsToView rs = toView . CRUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs
|
||||||
groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [Group] -> Map ConnId GroupMember -> Bool -> m ()
|
groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [Group] -> Map ConnId GroupMember -> Bool -> m ()
|
||||||
@@ -2742,12 +2768,12 @@ subscribeUserConnections onlyNeeded agentBatchSubscribe user@User {userId} = do
|
|||||||
resultsFor rs = M.foldrWithKey' addResult []
|
resultsFor rs = M.foldrWithKey' addResult []
|
||||||
where
|
where
|
||||||
addResult :: ConnId -> a -> [(a, Maybe ChatError)] -> [(a, Maybe ChatError)]
|
addResult :: ConnId -> a -> [(a, Maybe ChatError)] -> [(a, Maybe ChatError)]
|
||||||
addResult connId = (:) . (,err)
|
addResult connId = (:) . (,resultErr connId rs)
|
||||||
where
|
resultErr :: ConnId -> Map ConnId (Either AgentErrorType ()) -> Maybe ChatError
|
||||||
err = case M.lookup connId rs of
|
resultErr connId rs = case M.lookup connId rs of
|
||||||
Just (Left e) -> Just $ ChatErrorAgent e Nothing
|
Just (Left e) -> Just $ ChatErrorAgent e Nothing
|
||||||
Just _ -> Nothing
|
Just _ -> Nothing
|
||||||
_ -> Just . ChatError . CEAgentNoSubResult $ AgentConnId connId
|
_ -> Just . ChatError . CEAgentNoSubResult $ AgentConnId connId
|
||||||
|
|
||||||
cleanupManager :: forall m. ChatMonad m => m ()
|
cleanupManager :: forall m. ChatMonad m => m ()
|
||||||
cleanupManager = do
|
cleanupManager = do
|
||||||
@@ -2892,16 +2918,22 @@ processAgentMessageNoConn :: forall m. ChatMonad m => ACommand 'Agent 'AENone ->
|
|||||||
processAgentMessageNoConn = \case
|
processAgentMessageNoConn = \case
|
||||||
CONNECT p h -> hostEvent $ CRHostConnected p h
|
CONNECT p h -> hostEvent $ CRHostConnected p h
|
||||||
DISCONNECT p h -> hostEvent $ CRHostDisconnected p h
|
DISCONNECT p h -> hostEvent $ CRHostDisconnected p h
|
||||||
DOWN srv conns -> serverEvent srv conns CRContactsDisconnected
|
DOWN srv conns -> serverEvent srv conns NSDisconnected CRContactsDisconnected
|
||||||
UP srv conns -> serverEvent srv conns CRContactsSubscribed
|
UP srv conns -> serverEvent srv conns NSConnected CRContactsSubscribed
|
||||||
SUSPENDED -> toView CRChatSuspended
|
SUSPENDED -> toView CRChatSuspended
|
||||||
DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId
|
DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId
|
||||||
where
|
where
|
||||||
hostEvent :: ChatResponse -> m ()
|
hostEvent :: ChatResponse -> m ()
|
||||||
hostEvent = whenM (asks $ hostEvents . config) . toView
|
hostEvent = whenM (asks $ hostEvents . config) . toView
|
||||||
serverEvent srv conns event = do
|
serverEvent srv conns nsStatus event = ifM (asks $ coreApi . config) notifyAPI notifyCLI
|
||||||
cs <- withStore' (`getConnectionsContacts` conns)
|
where
|
||||||
toView $ event srv cs
|
notifyAPI = do
|
||||||
|
let connIds = map AgentConnId conns
|
||||||
|
chatModifyVar connNetworkStatuses $ \m -> foldl' (\m' cId -> M.insert cId nsStatus m') m connIds
|
||||||
|
toView $ CRNetworkStatus nsStatus connIds
|
||||||
|
notifyCLI = do
|
||||||
|
cs <- withStore' (`getConnectionsContacts` conns)
|
||||||
|
toView $ event srv cs
|
||||||
|
|
||||||
processAgentMsgSndFile :: forall m. ChatMonad m => ACorrId -> SndFileId -> ACommand 'Agent 'AESndFile -> m ()
|
processAgentMsgSndFile :: forall m. ChatMonad m => ACorrId -> SndFileId -> ACommand 'Agent 'AESndFile -> m ()
|
||||||
processAgentMsgSndFile _corrId aFileId msg =
|
processAgentMsgSndFile _corrId aFileId msg =
|
||||||
@@ -3188,6 +3220,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
Nothing -> do
|
Nothing -> do
|
||||||
-- [incognito] print incognito profile used for this contact
|
-- [incognito] print incognito profile used for this contact
|
||||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||||
|
setContactNetworkStatus ct NSConnected
|
||||||
toView $ CRContactConnected user ct (fmap fromLocalProfile incognitoProfile)
|
toView $ CRContactConnected user ct (fmap fromLocalProfile incognitoProfile)
|
||||||
when (directOrUsed ct) $ createFeatureEnabledItems ct
|
when (directOrUsed ct) $ createFeatureEnabledItems ct
|
||||||
when (contactConnInitiated conn) $ do
|
when (contactConnInitiated conn) $ do
|
||||||
@@ -3762,6 +3795,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
notifyMemberConnected :: GroupInfo -> GroupMember -> Maybe Contact -> m ()
|
notifyMemberConnected :: GroupInfo -> GroupMember -> Maybe Contact -> m ()
|
||||||
notifyMemberConnected gInfo m ct_ = do
|
notifyMemberConnected gInfo m ct_ = do
|
||||||
memberConnectedChatItem gInfo m
|
memberConnectedChatItem gInfo m
|
||||||
|
mapM_ (`setContactNetworkStatus` NSConnected) ct_
|
||||||
toView $ CRConnectedToGroupMember user gInfo m ct_
|
toView $ CRConnectedToGroupMember user gInfo m ct_
|
||||||
|
|
||||||
probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> m ()
|
probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> m ()
|
||||||
@@ -5569,6 +5603,7 @@ chatCommandP =
|
|||||||
"/_call end @" *> (APIEndCall <$> A.decimal),
|
"/_call end @" *> (APIEndCall <$> A.decimal),
|
||||||
"/_call status @" *> (APICallStatus <$> A.decimal <* A.space <*> strP),
|
"/_call status @" *> (APICallStatus <$> A.decimal <* A.space <*> strP),
|
||||||
"/_call get" $> APIGetCallInvitations,
|
"/_call get" $> APIGetCallInvitations,
|
||||||
|
"/_network_statuses" $> APIGetNetworkStatuses,
|
||||||
"/_profile " *> (APIUpdateProfile <$> A.decimal <* A.space <*> jsonP),
|
"/_profile " *> (APIUpdateProfile <$> A.decimal <* A.space <*> jsonP),
|
||||||
"/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
"/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
||||||
"/_set alias :" *> (APISetConnectionAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
"/_set alias :" *> (APISetConnectionAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import Data.Char (ord)
|
|||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
|
import qualified Data.Map.Strict as M
|
||||||
import Data.String
|
import Data.String
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Time (NominalDiffTime, UTCTime)
|
import Data.Time (NominalDiffTime, UTCTime)
|
||||||
@@ -124,7 +125,8 @@ data ChatConfig = ChatConfig
|
|||||||
initialCleanupManagerDelay :: Int64,
|
initialCleanupManagerDelay :: Int64,
|
||||||
cleanupManagerInterval :: NominalDiffTime,
|
cleanupManagerInterval :: NominalDiffTime,
|
||||||
cleanupManagerStepDelay :: Int64,
|
cleanupManagerStepDelay :: Int64,
|
||||||
ciExpirationInterval :: Int64 -- microseconds
|
ciExpirationInterval :: Int64, -- microseconds
|
||||||
|
coreApi :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
data DefaultAgentServers = DefaultAgentServers
|
data DefaultAgentServers = DefaultAgentServers
|
||||||
@@ -164,6 +166,7 @@ data ChatController = ChatController
|
|||||||
idsDrg :: TVar ChaChaDRG,
|
idsDrg :: TVar ChaChaDRG,
|
||||||
inputQ :: TBQueue String,
|
inputQ :: TBQueue String,
|
||||||
outputQ :: TBQueue (Maybe CorrId, ChatResponse),
|
outputQ :: TBQueue (Maybe CorrId, ChatResponse),
|
||||||
|
connNetworkStatuses :: TMap AgentConnId NetworkStatus,
|
||||||
subscriptionMode :: TVar SubscriptionMode,
|
subscriptionMode :: TVar SubscriptionMode,
|
||||||
chatLock :: Lock,
|
chatLock :: Lock,
|
||||||
sndFiles :: TVar (Map Int64 Handle),
|
sndFiles :: TVar (Map Int64 Handle),
|
||||||
@@ -251,6 +254,7 @@ data ChatCommand
|
|||||||
| APIEndCall ContactId
|
| APIEndCall ContactId
|
||||||
| APIGetCallInvitations
|
| APIGetCallInvitations
|
||||||
| APICallStatus ContactId WebRTCCallStatus
|
| APICallStatus ContactId WebRTCCallStatus
|
||||||
|
| APIGetNetworkStatuses
|
||||||
| APIUpdateProfile UserId Profile
|
| APIUpdateProfile UserId Profile
|
||||||
| APISetContactPrefs ContactId Preferences
|
| APISetContactPrefs ContactId Preferences
|
||||||
| APISetContactAlias ContactId LocalAlias
|
| APISetContactAlias ContactId LocalAlias
|
||||||
@@ -528,6 +532,8 @@ data ChatResponse
|
|||||||
| CRContactSubError {user :: User, contact :: Contact, chatError :: ChatError}
|
| CRContactSubError {user :: User, contact :: Contact, chatError :: ChatError}
|
||||||
| CRContactSubSummary {user :: User, contactSubscriptions :: [ContactSubStatus]}
|
| CRContactSubSummary {user :: User, contactSubscriptions :: [ContactSubStatus]}
|
||||||
| CRUserContactSubSummary {user :: User, userContactSubscriptions :: [UserContactSubStatus]}
|
| CRUserContactSubSummary {user :: User, userContactSubscriptions :: [UserContactSubStatus]}
|
||||||
|
| CRNetworkStatus {networkStatus :: NetworkStatus, connections :: [AgentConnId]}
|
||||||
|
| CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]}
|
||||||
| CRHostConnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
| CRHostConnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||||
| CRHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
| CRHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost}
|
||||||
| CRGroupInvitation {user :: User, groupInfo :: GroupInfo}
|
| CRGroupInvitation {user :: User, groupInfo :: GroupInfo}
|
||||||
@@ -1044,6 +1050,13 @@ chatWriteVar :: ChatMonad' m => (ChatController -> TVar a) -> a -> m ()
|
|||||||
chatWriteVar f value = asks f >>= atomically . (`writeTVar` value)
|
chatWriteVar f value = asks f >>= atomically . (`writeTVar` value)
|
||||||
{-# INLINE chatWriteVar #-}
|
{-# INLINE chatWriteVar #-}
|
||||||
|
|
||||||
|
chatModifyVar :: ChatMonad' m => (ChatController -> TVar a) -> (a -> a) -> m ()
|
||||||
|
chatModifyVar f newValue = asks f >>= atomically . (`modifyTVar'` newValue)
|
||||||
|
{-# INLINE chatModifyVar #-}
|
||||||
|
|
||||||
|
setContactNetworkStatus :: ChatMonad' m => Contact -> NetworkStatus -> m ()
|
||||||
|
setContactNetworkStatus ct = chatModifyVar connNetworkStatuses . M.insert (contactAgentConnId ct)
|
||||||
|
|
||||||
tryChatError :: ChatMonad m => m a -> m (Either ChatError a)
|
tryChatError :: ChatMonad m => m a -> m (Either ChatError a)
|
||||||
tryChatError = tryAllErrors mkChatError
|
tryChatError = tryAllErrors mkChatError
|
||||||
{-# INLINE tryChatError #-}
|
{-# INLINE tryChatError #-}
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ defaultMobileConfig :: ChatConfig
|
|||||||
defaultMobileConfig =
|
defaultMobileConfig =
|
||||||
defaultChatConfig
|
defaultChatConfig
|
||||||
{ confirmMigrations = MCYesUp,
|
{ confirmMigrations = MCYesUp,
|
||||||
logLevel = CLLError
|
logLevel = CLLError,
|
||||||
|
coreApi = True
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveUser_ :: SQLiteStore -> IO (Maybe User)
|
getActiveUser_ :: SQLiteStore -> IO (Maybe User)
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ instance ToJSON Contact where
|
|||||||
contactConn :: Contact -> Connection
|
contactConn :: Contact -> Connection
|
||||||
contactConn Contact {activeConn} = activeConn
|
contactConn Contact {activeConn} = activeConn
|
||||||
|
|
||||||
|
contactAgentConnId :: Contact -> AgentConnId
|
||||||
|
contactAgentConnId Contact {activeConn = Connection {agentConnId}} = agentConnId
|
||||||
|
|
||||||
contactConnId :: Contact -> ConnId
|
contactConnId :: Contact -> ConnId
|
||||||
contactConnId = aConnId . contactConn
|
contactConnId = aConnId . contactConn
|
||||||
|
|
||||||
@@ -1140,13 +1143,16 @@ liveRcvFileTransferPath ft = fp <$> liveRcvFileTransferInfo ft
|
|||||||
fp RcvFileInfo {filePath} = filePath
|
fp RcvFileInfo {filePath} = filePath
|
||||||
|
|
||||||
newtype AgentConnId = AgentConnId ConnId
|
newtype AgentConnId = AgentConnId ConnId
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
instance StrEncoding AgentConnId where
|
instance StrEncoding AgentConnId where
|
||||||
strEncode (AgentConnId connId) = strEncode connId
|
strEncode (AgentConnId connId) = strEncode connId
|
||||||
strDecode s = AgentConnId <$> strDecode s
|
strDecode s = AgentConnId <$> strDecode s
|
||||||
strP = AgentConnId <$> strP
|
strP = AgentConnId <$> strP
|
||||||
|
|
||||||
|
instance FromJSON AgentConnId where
|
||||||
|
parseJSON = strParseJSON "AgentConnId"
|
||||||
|
|
||||||
instance ToJSON AgentConnId where
|
instance ToJSON AgentConnId where
|
||||||
toJSON = strToJSON
|
toJSON = strToJSON
|
||||||
toEncoding = strToJEncoding
|
toEncoding = strToJEncoding
|
||||||
@@ -1477,6 +1483,35 @@ serializeIntroStatus = \case
|
|||||||
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
||||||
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode
|
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode
|
||||||
|
|
||||||
|
data NetworkStatus
|
||||||
|
= NSUnknown
|
||||||
|
| NSConnected
|
||||||
|
| NSDisconnected
|
||||||
|
| NSError {connectionError :: String}
|
||||||
|
deriving (Eq, Ord, Show, Generic)
|
||||||
|
|
||||||
|
netStatusStr :: NetworkStatus -> String
|
||||||
|
netStatusStr = \case
|
||||||
|
NSUnknown -> "unknown"
|
||||||
|
NSConnected -> "connected"
|
||||||
|
NSDisconnected -> "disconnected"
|
||||||
|
NSError e -> "error: " <> e
|
||||||
|
|
||||||
|
instance FromJSON NetworkStatus where
|
||||||
|
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "NS"
|
||||||
|
|
||||||
|
instance ToJSON NetworkStatus where
|
||||||
|
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "NS"
|
||||||
|
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "NS"
|
||||||
|
|
||||||
|
data ConnNetworkStatus = ConnNetworkStatus
|
||||||
|
{ agentConnId :: AgentConnId,
|
||||||
|
networkStatus :: NetworkStatus
|
||||||
|
}
|
||||||
|
deriving (Show, Generic, FromJSON)
|
||||||
|
|
||||||
|
instance ToJSON ConnNetworkStatus where toEncoding = J.genericToEncoding J.defaultOptions
|
||||||
|
|
||||||
type CommandId = Int64
|
type CommandId = Int64
|
||||||
|
|
||||||
aCorrId :: CommandId -> ACorrId
|
aCorrId :: CommandId -> ACorrId
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import Data.Char (isSpace, toUpper)
|
|||||||
import Data.Function (on)
|
import Data.Function (on)
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.List (groupBy, intercalate, intersperse, partition, sortOn)
|
import Data.List (groupBy, intercalate, intersperse, partition, sortOn)
|
||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty (..))
|
||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
@@ -210,6 +210,8 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
|
|||||||
(addresses, groupLinks) = partition (\UserContactSubStatus {userContact} -> isNothing . userContactGroupId $ userContact) summary
|
(addresses, groupLinks) = partition (\UserContactSubStatus {userContact} -> isNothing . userContactGroupId $ userContact) summary
|
||||||
addressSS UserContactSubStatus {userContactError} = maybe ("Your address is active! To show: " <> highlight' "/sa") (\e -> "User address error: " <> sShow e <> ", to delete your address: " <> highlight' "/da") userContactError
|
addressSS UserContactSubStatus {userContactError} = maybe ("Your address is active! To show: " <> highlight' "/sa") (\e -> "User address error: " <> sShow e <> ", to delete your address: " <> highlight' "/da") userContactError
|
||||||
(groupLinkErrors, groupLinksSubscribed) = partition (isJust . userContactError) groupLinks
|
(groupLinkErrors, groupLinksSubscribed) = partition (isJust . userContactError) groupLinks
|
||||||
|
CRNetworkStatus status conns -> if testView then [plain $ show (length conns) <> " connections " <> netStatusStr status] else []
|
||||||
|
CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else []
|
||||||
CRGroupInvitation u g -> ttyUser u [groupInvitation' g]
|
CRGroupInvitation u g -> ttyUser u [groupInvitation' g]
|
||||||
CRReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r
|
CRReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r
|
||||||
CRUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g
|
CRUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g
|
||||||
@@ -800,6 +802,12 @@ viewDirectMessagesProhibited :: MsgDirection -> Contact -> [StyledString]
|
|||||||
viewDirectMessagesProhibited MDSnd c = ["direct messages to indirect contact " <> ttyContact' c <> " are prohibited"]
|
viewDirectMessagesProhibited MDSnd c = ["direct messages to indirect contact " <> ttyContact' c <> " are prohibited"]
|
||||||
viewDirectMessagesProhibited MDRcv c = ["received prohibited direct message from indirect contact " <> ttyContact' c <> " (discarded)"]
|
viewDirectMessagesProhibited MDRcv c = ["received prohibited direct message from indirect contact " <> ttyContact' c <> " (discarded)"]
|
||||||
|
|
||||||
|
viewNetworkStatuses :: [ConnNetworkStatus] -> [StyledString]
|
||||||
|
viewNetworkStatuses = map viewStatuses . L.groupBy ((==) `on` netStatus) . sortOn netStatus
|
||||||
|
where
|
||||||
|
netStatus ConnNetworkStatus {networkStatus} = networkStatus
|
||||||
|
viewStatuses ss@(s :| _) = plain $ show (L.length ss) <> " connections " <> netStatusStr (netStatus s)
|
||||||
|
|
||||||
viewUserJoinedGroup :: GroupInfo -> [StyledString]
|
viewUserJoinedGroup :: GroupInfo -> [StyledString]
|
||||||
viewUserJoinedGroup g =
|
viewUserJoinedGroup g =
|
||||||
case incognitoMembershipProfile g of
|
case incognitoMembershipProfile g of
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ chatDirectTests = do
|
|||||||
testReqVRange vr11 supportedChatVRange
|
testReqVRange vr11 supportedChatVRange
|
||||||
testReqVRange vr11 vr11
|
testReqVRange vr11 vr11
|
||||||
it "update peer version range on received messages" testUpdatePeerChatVRange
|
it "update peer version range on received messages" testUpdatePeerChatVRange
|
||||||
|
describe "network statuses" $ do
|
||||||
|
it "should get network statuses" testGetNetworkStatuses
|
||||||
where
|
where
|
||||||
testInvVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnInvChatVRange vr1 vr2
|
testInvVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnInvChatVRange vr1 vr2
|
||||||
testReqVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnReqChatVRange vr1 vr2
|
testReqVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnReqChatVRange vr1 vr2
|
||||||
@@ -2623,6 +2625,20 @@ testUpdatePeerChatVRange tmp =
|
|||||||
where
|
where
|
||||||
cfg11 = testCfg {chatVRange = vr11} :: ChatConfig
|
cfg11 = testCfg {chatVRange = vr11} :: ChatConfig
|
||||||
|
|
||||||
|
testGetNetworkStatuses :: HasCallStack => FilePath -> IO ()
|
||||||
|
testGetNetworkStatuses tmp = do
|
||||||
|
withNewTestChatCfg tmp cfg "alice" aliceProfile $ \alice -> do
|
||||||
|
withNewTestChatCfg tmp cfg "bob" bobProfile $ \bob -> do
|
||||||
|
connectUsers alice bob
|
||||||
|
alice ##> "/_network_statuses"
|
||||||
|
alice <## "1 connections connected"
|
||||||
|
withTestChatCfg tmp cfg "alice" $ \alice ->
|
||||||
|
withTestChatCfg tmp cfg "bob" $ \bob -> do
|
||||||
|
alice <## "1 connections connected"
|
||||||
|
bob <## "1 connections connected"
|
||||||
|
where
|
||||||
|
cfg = testCfg {coreApi = True}
|
||||||
|
|
||||||
vr11 :: VersionRange
|
vr11 :: VersionRange
|
||||||
vr11 = mkVersionRange 1 1
|
vr11 = mkVersionRange 1 1
|
||||||
|
|
||||||
|
|||||||
@@ -120,19 +120,19 @@ chatStartedSwift = "{\"resp\":{\"_owsf\":true,\"chatStarted\":{}}}"
|
|||||||
chatStartedTagged :: LB.ByteString
|
chatStartedTagged :: LB.ByteString
|
||||||
chatStartedTagged = "{\"resp\":{\"type\":\"chatStarted\"}}"
|
chatStartedTagged = "{\"resp\":{\"type\":\"chatStarted\"}}"
|
||||||
|
|
||||||
contactSubSummary :: LB.ByteString
|
networkStatuses :: LB.ByteString
|
||||||
contactSubSummary =
|
networkStatuses =
|
||||||
#if defined(darwin_HOST_OS) && defined(swiftJSON)
|
#if defined(darwin_HOST_OS) && defined(swiftJSON)
|
||||||
contactSubSummarySwift
|
networkStatusesSwift
|
||||||
#else
|
#else
|
||||||
contactSubSummaryTagged
|
networkStatusesTagged
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
contactSubSummarySwift :: LB.ByteString
|
networkStatusesSwift :: LB.ByteString
|
||||||
contactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"contactSubSummary\":{" <> userJSON <> ",\"contactSubscriptions\":[]}}}"
|
networkStatusesSwift = "{\"resp\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}"
|
||||||
|
|
||||||
contactSubSummaryTagged :: LB.ByteString
|
networkStatusesTagged :: LB.ByteString
|
||||||
contactSubSummaryTagged = "{\"resp\":{\"type\":\"contactSubSummary\"," <> userJSON <> ",\"contactSubscriptions\":[]}}"
|
networkStatusesTagged = "{\"resp\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}"
|
||||||
|
|
||||||
memberSubSummary :: LB.ByteString
|
memberSubSummary :: LB.ByteString
|
||||||
memberSubSummary =
|
memberSubSummary =
|
||||||
@@ -143,10 +143,10 @@ memberSubSummary =
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
memberSubSummarySwift :: LB.ByteString
|
memberSubSummarySwift :: LB.ByteString
|
||||||
memberSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"memberSubSummary\":{" <> userJSON <> ",\"memberSubscriptions\":[]}}}"
|
memberSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}"
|
||||||
|
|
||||||
memberSubSummaryTagged :: LB.ByteString
|
memberSubSummaryTagged :: LB.ByteString
|
||||||
memberSubSummaryTagged = "{\"resp\":{\"type\":\"memberSubSummary\"," <> userJSON <> ",\"memberSubscriptions\":[]}}"
|
memberSubSummaryTagged = "{\"resp\":{\"type\":\"memberSubSummary\",\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}"
|
||||||
|
|
||||||
userContactSubSummary :: LB.ByteString
|
userContactSubSummary :: LB.ByteString
|
||||||
userContactSubSummary =
|
userContactSubSummary =
|
||||||
@@ -157,10 +157,10 @@ userContactSubSummary =
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
userContactSubSummarySwift :: LB.ByteString
|
userContactSubSummarySwift :: LB.ByteString
|
||||||
userContactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"userContactSubSummary\":{" <> userJSON <> ",\"userContactSubscriptions\":[]}}}"
|
userContactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"userContactSubSummary\":{\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}}"
|
||||||
|
|
||||||
userContactSubSummaryTagged :: LB.ByteString
|
userContactSubSummaryTagged :: LB.ByteString
|
||||||
userContactSubSummaryTagged = "{\"resp\":{\"type\":\"userContactSubSummary\"," <> userJSON <> ",\"userContactSubscriptions\":[]}}"
|
userContactSubSummaryTagged = "{\"resp\":{\"type\":\"userContactSubSummary\",\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}"
|
||||||
|
|
||||||
pendingSubSummary :: LB.ByteString
|
pendingSubSummary :: LB.ByteString
|
||||||
pendingSubSummary =
|
pendingSubSummary =
|
||||||
@@ -171,13 +171,13 @@ pendingSubSummary =
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
pendingSubSummarySwift :: LB.ByteString
|
pendingSubSummarySwift :: LB.ByteString
|
||||||
pendingSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"pendingSubSummary\":{" <> userJSON <> ",\"pendingSubscriptions\":[]}}}"
|
pendingSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"pendingSubSummary\":{\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}}"
|
||||||
|
|
||||||
pendingSubSummaryTagged :: LB.ByteString
|
pendingSubSummaryTagged :: LB.ByteString
|
||||||
pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\"," <> userJSON <> ",\"pendingSubscriptions\":[]}}"
|
pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}"
|
||||||
|
|
||||||
userJSON :: LB.ByteString
|
userJSON :: LB.ByteString
|
||||||
userJSON = "\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}"
|
userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}"
|
||||||
|
|
||||||
parsedMarkdown :: LB.ByteString
|
parsedMarkdown :: LB.ByteString
|
||||||
parsedMarkdown =
|
parsedMarkdown =
|
||||||
@@ -215,7 +215,7 @@ testChatApi tmp = do
|
|||||||
chatSendCmd cc "/u" `shouldReturn` activeUser
|
chatSendCmd cc "/u" `shouldReturn` activeUser
|
||||||
chatSendCmd cc "/create user alice Alice" `shouldReturn` activeUserExists
|
chatSendCmd cc "/create user alice Alice" `shouldReturn` activeUserExists
|
||||||
chatSendCmd cc "/_start" `shouldReturn` chatStarted
|
chatSendCmd cc "/_start" `shouldReturn` chatStarted
|
||||||
chatRecvMsg cc `shouldReturn` contactSubSummary
|
chatRecvMsg cc `shouldReturn` networkStatuses
|
||||||
chatRecvMsg cc `shouldReturn` userContactSubSummary
|
chatRecvMsg cc `shouldReturn` userContactSubSummary
|
||||||
chatRecvMsg cc `shouldReturn` memberSubSummary
|
chatRecvMsg cc `shouldReturn` memberSubSummary
|
||||||
chatRecvMsgWait cc 10000 `shouldReturn` pendingSubSummary
|
chatRecvMsgWait cc 10000 `shouldReturn` pendingSubSummary
|
||||||
|
|||||||
Reference in New Issue
Block a user