mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-11 20:38:55 +00:00
core: chat item content types for chat initiation (#5998)
* core: chat item content types for chat initiation * connection mode for ui * padding * simplexmq * initial items * update content items * core: feature and e2e items * refactor * chat items * ios types * fix condition for PQ encryption of link
This commit is contained in:
@@ -1350,7 +1350,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
|
||||
var fileIdsToApprove: [Int64] = []
|
||||
var srvsToApprove: Set<String> = []
|
||||
var otherFileErrs: [APIResult<ChatResponse2>] = []
|
||||
|
||||
|
||||
for fileId in fileIds {
|
||||
let r: APIResult<ChatResponse2> = await chatApiSendCmd(
|
||||
.receiveFile(
|
||||
@@ -1374,7 +1374,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
|
||||
otherFileErrs.append(r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !auto {
|
||||
let otherErrsStr = fileErrorStrs(otherFileErrs)
|
||||
// If there are not approved files, alert is shown the same way both in case of singular and plural files reception
|
||||
@@ -1439,7 +1439,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func fileErrorStrs(_ errs: [APIResult<ChatResponse2>]) -> String {
|
||||
var errStr = ""
|
||||
if errs.count >= 1 {
|
||||
@@ -1454,7 +1454,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
|
||||
return errStr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cancelFile(user: User, fileId: Int64) async {
|
||||
if let chatItem = await apiCancelFile(fileId: fileId) {
|
||||
await chatItemSimpleUpdate(user, chatItem)
|
||||
|
||||
@@ -241,16 +241,21 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func directE2EEInfoText(_ info: E2EEInfo) -> Text {
|
||||
info.pqEnabled
|
||||
? Text("Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
.font(.caption)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.fontWeight(.light)
|
||||
: e2eeInfoNoPQText()
|
||||
if let pqEnabled = info.pqEnabled {
|
||||
pqEnabled
|
||||
? e2eeInfoText("Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
: e2eeInfoNoPQText()
|
||||
} else {
|
||||
e2eeInfoText("Messages are protected by **end-to-end encryption**.")
|
||||
}
|
||||
}
|
||||
|
||||
private func e2eeInfoNoPQText() -> Text {
|
||||
Text("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
}
|
||||
|
||||
private func e2eeInfoText(_ s: LocalizedStringKey) -> Text {
|
||||
Text(s)
|
||||
.font(.caption)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.fontWeight(.light)
|
||||
|
||||
@@ -402,7 +402,7 @@ struct ComposeView: View {
|
||||
case (true, .voicePreview): EmptyView() // ? we may allow playback when editing is allowed
|
||||
default: previewView()
|
||||
}
|
||||
HStack (alignment: .bottom) {
|
||||
HStack (alignment: .center) {
|
||||
if !chat.chatInfo.nextConnect {
|
||||
attachmentButton()
|
||||
}
|
||||
@@ -411,7 +411,6 @@ struct ComposeView: View {
|
||||
|
||||
if chat.chatInfo.nextConnect {
|
||||
nextConnectButton()
|
||||
.padding(.bottom, 16)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
@@ -634,7 +633,6 @@ struct ComposeView: View {
|
||||
}
|
||||
.disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.bottom, 16)
|
||||
.tint(theme.colors.primary)
|
||||
if im.secondaryIMFilter == nil,
|
||||
case let .group(g, _) = chat.chatInfo,
|
||||
@@ -1085,6 +1083,9 @@ struct ComposeView: View {
|
||||
return .file(msgText)
|
||||
case .report(_, let reason):
|
||||
return .report(text: msgText, reason: reason)
|
||||
// TODO [short links] update chat link
|
||||
case let .chat(_, chatLink):
|
||||
return .chat(text: msgText, chatLink: chatLink)
|
||||
case .unknown(let type, _):
|
||||
return .unknown(type: type, text: msgText)
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -543,8 +543,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -704,8 +704,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -790,8 +790,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-LLcXQT7lD7m57CUQwpBZ6W.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -1718,7 +1718,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
var chatTs: Date?
|
||||
public var connLinkToConnect: CreatedConnLink?
|
||||
public var preparedContact: PreparedContact?
|
||||
public var contactRequestId: Int64?
|
||||
var contactGroupMemberId: Int64?
|
||||
var contactGrpInvSent: Bool
|
||||
@@ -1733,9 +1733,9 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } }
|
||||
public var active: Bool { get { contactStatus == .active } }
|
||||
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
|
||||
public var nextConnectPrepared: Bool { get { connLinkToConnect != nil && activeConn == nil } }
|
||||
public var nextConnectPrepared: Bool { get { preparedContact != nil && activeConn == nil } }
|
||||
public var nextAcceptContactRequest: Bool { get { contactRequestId != nil && activeConn == nil } }
|
||||
public var sendMsgToConnect: Bool { get { nextSendGrpInv || nextConnectPrepared } }
|
||||
public var sendMsgToConnect: Bool { nextSendGrpInv || preparedContact != nil }
|
||||
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
public var image: String? { get { profile.image } }
|
||||
@@ -1793,6 +1793,16 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
public struct PreparedContact: Decodable, Hashable {
|
||||
public var connLinkToConnect: CreatedConnLink
|
||||
public var uiConnLinkType: ConnectionMode
|
||||
}
|
||||
|
||||
public enum ConnectionMode: String, Decodable, Hashable {
|
||||
case inv
|
||||
case con
|
||||
}
|
||||
|
||||
public enum ContactStatus: String, Decodable, Hashable {
|
||||
case active = "active"
|
||||
case deleted = "deleted"
|
||||
@@ -2191,6 +2201,7 @@ public enum MemberCriteria: String, Codable, Identifiable, Hashable {
|
||||
public struct ContactShortLinkData: Codable, Hashable {
|
||||
public var profile: Profile
|
||||
public var message: String?
|
||||
public var business: Bool
|
||||
}
|
||||
|
||||
public struct GroupShortLinkData: Codable, Hashable {
|
||||
@@ -3623,7 +3634,7 @@ public enum CIContent: Decodable, ItemContent, Hashable {
|
||||
}
|
||||
|
||||
private func directE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String {
|
||||
e2eeInfo.pqEnabled
|
||||
e2eeInfo.pqEnabled == true
|
||||
? NSLocalizedString("This chat is protected by quantum resistant end-to-end encryption.", comment: "E2EE info chat item")
|
||||
: e2eeInfoNoPQStr
|
||||
}
|
||||
@@ -4104,6 +4115,7 @@ public enum MsgContent: Equatable, Hashable {
|
||||
case voice(text: String, duration: Int)
|
||||
case file(String)
|
||||
case report(text: String, reason: ReportReason)
|
||||
case chat(text: String, chatLink: MsgChatLink)
|
||||
// TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift
|
||||
case unknown(type: String, text: String)
|
||||
|
||||
@@ -4116,6 +4128,7 @@ public enum MsgContent: Equatable, Hashable {
|
||||
case let .voice(text, _): return text
|
||||
case let .file(text): return text
|
||||
case let .report(text, _): return text
|
||||
case let .chat(text, _): return text
|
||||
case let .unknown(_, text): return text
|
||||
}
|
||||
}
|
||||
@@ -4176,6 +4189,7 @@ public enum MsgContent: Equatable, Hashable {
|
||||
case image
|
||||
case duration
|
||||
case reason
|
||||
case chatLink
|
||||
}
|
||||
|
||||
public static func == (lhs: MsgContent, rhs: MsgContent) -> Bool {
|
||||
@@ -4187,6 +4201,7 @@ public enum MsgContent: Equatable, Hashable {
|
||||
case let (.voice(lt, ld), .voice(rt, rd)): return lt == rt && ld == rd
|
||||
case let (.file(lf), .file(rf)): return lf == rf
|
||||
case let (.report(lt, lr), .report(rt, rr)): return lt == rt && lr == rr
|
||||
case let (.chat(lt, ll), .chat(rt, rl)): return lt == rt && ll == rl
|
||||
case let (.unknown(lType, lt), .unknown(rType, rt)): return lType == rType && lt == rt
|
||||
default: return false
|
||||
}
|
||||
@@ -4226,6 +4241,10 @@ extension MsgContent: Decodable {
|
||||
let text = try container.decode(String.self, forKey: CodingKeys.text)
|
||||
let reason = try container.decode(ReportReason.self, forKey: CodingKeys.reason)
|
||||
self = .report(text: text, reason: reason)
|
||||
case "chat":
|
||||
let text = try container.decode(String.self, forKey: CodingKeys.text)
|
||||
let chatLink = try container.decode(MsgChatLink.self, forKey: CodingKeys.chatLink)
|
||||
self = .chat(text: text, chatLink: chatLink)
|
||||
default:
|
||||
let text = try? container.decode(String.self, forKey: CodingKeys.text)
|
||||
self = .unknown(type: type, text: text ?? "unknown message format")
|
||||
@@ -4267,6 +4286,10 @@ extension MsgContent: Encodable {
|
||||
try container.encode("report", forKey: .type)
|
||||
try container.encode(text, forKey: .text)
|
||||
try container.encode(reason, forKey: .reason)
|
||||
case let .chat(text, chatLink):
|
||||
try container.encode("chat", forKey: .type)
|
||||
try container.encode(text, forKey: .text)
|
||||
try container.encode(chatLink, forKey: .chatLink)
|
||||
// TODO use original JSON and type
|
||||
case let .unknown(_, text):
|
||||
try container.encode("text", forKey: .type)
|
||||
@@ -4285,6 +4308,12 @@ public enum MsgContentTag: String {
|
||||
case report
|
||||
}
|
||||
|
||||
public enum MsgChatLink: Codable, Equatable, Hashable {
|
||||
case contact(connLink: String, profile: Profile, business: Bool)
|
||||
case invitation(invLink: String, profile: Profile)
|
||||
case group(connLink: String, groupProfile: GroupProfile)
|
||||
}
|
||||
|
||||
public struct FormattedText: Decodable, Hashable {
|
||||
public var text: String
|
||||
public var format: Format?
|
||||
@@ -4587,7 +4616,7 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable {
|
||||
}
|
||||
|
||||
public struct E2EEInfo: Decodable, Hashable {
|
||||
public var pqEnabled: Bool
|
||||
public var pqEnabled: Bool?
|
||||
}
|
||||
|
||||
public enum RcvDirectEvent: Decodable, Hashable {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: c5b7d3c7afb8c0df8d329885db09b08c2e88109c
|
||||
tag: af34e80729ea24769d0b707824a4d840f70273cc
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."c5b7d3c7afb8c0df8d329885db09b08c2e88109c" = "078bjnw5ypyqlldqy9g49y0y6k3xbg0hckskrg40iw06mc8wj174";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."af34e80729ea24769d0b707824a4d840f70273cc" = "0pbkpinm1bwna9f5ix8kimg15fppsqyawn2pijl3xxyjhv0vgwdq";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
@@ -451,7 +451,7 @@ data ChatCommand
|
||||
| APIChangeConnectionUser Int64 UserId -- new user id to switch connection to
|
||||
| APIConnectPlan UserId AConnectionLink
|
||||
| APIPrepareContact UserId ACreatedConnLink ContactShortLinkData
|
||||
| APIPrepareGroup UserId ACreatedConnLink GroupShortLinkData
|
||||
| APIPrepareGroup UserId CreatedLinkContact GroupShortLinkData
|
||||
| APIChangePreparedContactUser ContactId UserId
|
||||
| APIChangePreparedGroupUser GroupId UserId
|
||||
| APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent}
|
||||
|
||||
@@ -99,7 +99,7 @@ import Simplex.Messaging.Compression (compressionLevel)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern IKPQOn, pattern PQEncOff, pattern PQSupportOff, pattern PQSupportOn)
|
||||
import Simplex.Messaging.Crypto.Ratchet (E2ERatchetParamsUri (..), PQEncryption (..), PQSupport (..), pattern IKPQOff, pattern IKPQOn, pattern PQEncOff, pattern PQSupportOff, pattern PQSupportOn, pqRatchetE2EEncryptVersion)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (base64P)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol)
|
||||
@@ -849,6 +849,7 @@ processChatCommand' vr = \case
|
||||
MCVoice {text} -> text /= ""
|
||||
MCFile t -> t /= ""
|
||||
MCReport {} -> True
|
||||
MCChat {} -> True
|
||||
MCUnknown {} -> True
|
||||
-- TODO [knocking] forward from / to scope
|
||||
APIForwardChatItems toChat@(ChatRef toCType toChatId toScope) fromChat@(ChatRef fromCType fromChatId _fromScope) itemIds itemTTL -> withUser $ \user -> case toCType of
|
||||
@@ -1734,19 +1735,32 @@ processChatCommand' vr = \case
|
||||
pure conn'
|
||||
APIConnectPlan userId cLink -> withUserId userId $ \user ->
|
||||
uncurry (CRConnectionPlan user) <$> connectPlan user cLink
|
||||
APIPrepareContact userId accLink contactSLinkData -> withUserId userId $ \user -> do
|
||||
let ContactShortLinkData {profile, message} = contactSLinkData
|
||||
APIPrepareContact userId accLink@(ACCL cMode (CCLink _ shortLink)) contactSLinkData -> withUserId userId $ \user -> do
|
||||
let ContactShortLinkData {profile, message, business} = contactSLinkData
|
||||
ct <- withStore $ \db -> createPreparedContact db user profile accLink
|
||||
forM_ message $ \msg ->
|
||||
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent $ MCText msg) Nothing
|
||||
let cMode' = connMode cMode
|
||||
createItem content = void $ createInternalItemForChat user (CDDirectRcv ct) content Nothing
|
||||
msgChatLink = \case
|
||||
sl@CSLContact {} -> MCLContact sl profile business
|
||||
sl@CSLInvitation {} -> MCLInvitation sl profile
|
||||
mapM_ (\sl -> createItem $ CIRcvMsgContent $ MCChat (safeDecodeUtf8 $ strEncode sl) $ msgChatLink sl) shortLink
|
||||
createItem $ CIRcvDirectE2EEInfo $ E2EInfo $ connLinkPQEncryption accLink
|
||||
void $ createFeatureEnabledItems_ user ct
|
||||
mapM_ (createItem . CIRcvMsgContent . MCText) message
|
||||
pure $ CRNewPreparedContact user ct
|
||||
APIPrepareGroup userId accLink groupSLinkData -> withUserId userId $ \user -> do
|
||||
let GroupShortLinkData {groupProfile} = groupSLinkData
|
||||
gInfo <- withStore $ \db -> createPreparedGroup db vr user groupProfile accLink
|
||||
APIPrepareGroup userId ccLink@(CCLink _ shortLink) groupSLinkData -> withUserId userId $ \user -> do
|
||||
let GroupShortLinkData {groupProfile = gp@GroupProfile {description}} = groupSLinkData
|
||||
gInfo <- withStore $ \db -> createPreparedGroup db vr user gp ccLink
|
||||
-- TODO use received item without member
|
||||
let cd = CDGroupRcv gInfo Nothing $ membership gInfo
|
||||
createItem content = void $ createInternalItemForChat user cd content Nothing
|
||||
mapM_ (\sl -> createItem $ CIRcvMsgContent $ MCChat (safeDecodeUtf8 $ strEncode sl) $ MCLGroup sl gp) shortLink
|
||||
void $ createGroupFeatureItems_ user cd CIRcvGroupFeature gInfo
|
||||
mapM_ (createItem . CIRcvMsgContent . MCText) description
|
||||
pure $ CRNewPreparedGroup user gInfo
|
||||
APIChangePreparedContactUser contactId newUserId -> withUser $ \user -> do
|
||||
ct@Contact {connLinkToConnect} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
when (isNothing connLinkToConnect) $ throwCmdError "contact doesn't have link to connect"
|
||||
ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
when (isNothing preparedContact) $ throwCmdError "contact doesn't have link to connect"
|
||||
when (isJust $ contactConn ct) $ throwCmdError "contact already has connection"
|
||||
newUser <- privateGetUser newUserId
|
||||
ct' <- withFastStore $ \db -> updatePreparedContactUser db vr user ct newUser
|
||||
@@ -1760,10 +1774,10 @@ processChatCommand' vr = \case
|
||||
gInfo' <- withFastStore $ \db -> updatePreparedGroupUser db vr user gInfo hostMember newUser
|
||||
pure $ CRGroupUserChanged user gInfo newUser gInfo'
|
||||
APIConnectPreparedContact contactId incognito msgContent_ -> withUser $ \user -> do
|
||||
Contact {connLinkToConnect} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
case connLinkToConnect of
|
||||
Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
|
||||
case preparedContact of
|
||||
Nothing -> throwCmdError "contact doesn't have link to connect"
|
||||
Just (ACCL SCMInvitation ccLink) ->
|
||||
Just PreparedContact {connLinkToConnect = ACCL SCMInvitation ccLink} ->
|
||||
connectViaInvitation user incognito ccLink (Just contactId) >>= \case
|
||||
CRSentConfirmation {customUserProfile} -> do
|
||||
-- get updated contact with connection
|
||||
@@ -1775,7 +1789,7 @@ processChatCommand' vr = \case
|
||||
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci]
|
||||
pure $ CRStartedConnectionToContact user ct' customUserProfile
|
||||
cr -> pure cr
|
||||
Just (ACCL SCMContact ccLink) ->
|
||||
Just PreparedContact {connLinkToConnect = ACCL SCMContact ccLink} ->
|
||||
connectViaContact user incognito ccLink msgContent_ (Just $ ACCGContact contactId) >>= \case
|
||||
CRSentInvitation {customUserProfile} -> do
|
||||
-- get updated contact with connection
|
||||
@@ -2031,7 +2045,7 @@ processChatCommand' vr = \case
|
||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||
gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile
|
||||
let cd = CDGroupSnd gInfo Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
pure $ CRGroupCreated user gInfo
|
||||
NewGroup incognito gProfile -> withUser $ \User {userId} ->
|
||||
@@ -3438,9 +3452,10 @@ processChatCommand' vr = \case
|
||||
contactShortLinkData :: Profile -> Maybe Text -> CM UserLinkData
|
||||
contactShortLinkData p msg = do
|
||||
large <- chatReadVar useLargeLinkData
|
||||
-- TODO [short links] business
|
||||
let contactData
|
||||
| large = ContactShortLinkData p msg
|
||||
| otherwise = ContactShortLinkData p {fullName = "", image = Nothing, contactLink = Nothing} Nothing
|
||||
| large = ContactShortLinkData p msg False
|
||||
| otherwise = ContactShortLinkData p {fullName = "", image = Nothing, contactLink = Nothing} Nothing False
|
||||
pure $ encodeShortLinkData large contactData
|
||||
groupShortLinkData :: GroupProfile -> CM UserLinkData
|
||||
groupShortLinkData gp = do
|
||||
@@ -4441,7 +4456,7 @@ chatCommandP =
|
||||
"/contacts" $> ListContacts,
|
||||
"/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> strP),
|
||||
"/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP),
|
||||
"/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP),
|
||||
"/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <* A.space <*> jsonP),
|
||||
"/_set contact user @" *> (APIChangePreparedContactUser <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/_set group user #" *> (APIChangePreparedGroupUser <$> A.decimal <* A.space <*> A.decimal),
|
||||
"/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)),
|
||||
@@ -4561,6 +4576,10 @@ chatCommandP =
|
||||
(ACR m cReq) <- strP
|
||||
sLink_ <- optional (A.space *> strP)
|
||||
pure $ ACCL m (CCLink cReq sLink_)
|
||||
connLinkP' = do
|
||||
cReq <- strP
|
||||
sLink_ <- optional (A.space *> strP)
|
||||
pure $ CCLink cReq sLink_
|
||||
connLinkP_ =
|
||||
((Just <$> connLinkP) <|> A.takeTill (== ' ') $> Nothing)
|
||||
incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False
|
||||
|
||||
@@ -316,6 +316,7 @@ quoteContent mc qmc ciFile_
|
||||
MCVideo {} -> True
|
||||
MCVoice {} -> False
|
||||
MCReport {} -> False
|
||||
MCChat {} -> True
|
||||
MCUnknown {} -> True
|
||||
qText = msgContentText qmc
|
||||
getFileName :: CIFile d -> String
|
||||
@@ -1019,7 +1020,7 @@ acceptBusinessJoinRequestAsync
|
||||
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
|
||||
withStore' $ \db -> createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
|
||||
let cd = CDGroupSnd gInfo Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
pure (gInfo, clientMember)
|
||||
where
|
||||
@@ -1265,7 +1266,7 @@ createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (C
|
||||
createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' =
|
||||
flip catchChatError (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of
|
||||
(Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled')
|
||||
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo pqSndEnabled')
|
||||
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo $ Just pqSndEnabled')
|
||||
_ -> pure (ct, conn)
|
||||
where
|
||||
createPQItem ciContent = do
|
||||
@@ -1280,7 +1281,7 @@ updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Conta
|
||||
updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' =
|
||||
flip catchChatError (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of
|
||||
(Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled')
|
||||
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo pqRcvEnabled')
|
||||
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo $ Just pqRcvEnabled')
|
||||
_ -> pure (ct, conn)
|
||||
where
|
||||
updatePQ ciContent = do
|
||||
@@ -2261,6 +2262,12 @@ userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do
|
||||
let userPrefs = maybe (preferences' user) (const Nothing) incognitoProfile
|
||||
in (p' :: Profile) {preferences = Just . toChatPrefs $ mergePreferences (userPreferences <$> ct) userPrefs}
|
||||
|
||||
connLinkPQEncryption :: ACreatedConnLink -> Maybe PQEncryption
|
||||
connLinkPQEncryption (ACCL _ (CCLink cReq _)) = case cReq of
|
||||
CRContactUri _ -> Nothing
|
||||
CRInvitationUri _ (CR.E2ERatchetParamsUri vr' _ _ pq) ->
|
||||
Just $ PQEncryption $ maxVersion vr' >= CR.pqRatchetE2EEncryptVersion && isJust pq
|
||||
|
||||
createRcvFeatureItems :: User -> Contact -> Contact -> CM' ()
|
||||
createRcvFeatureItems user ct ct' =
|
||||
createFeatureItems user ct ct' CDDirectRcv CIRcvChatFeature CIRcvChatPreference contactPreference
|
||||
@@ -2275,6 +2282,15 @@ createSndFeatureItems user ct ct' =
|
||||
|
||||
type FeatureContent a d = ChatFeature -> a -> Maybe Int -> CIContent d
|
||||
|
||||
createFeatureEnabledItems :: User -> Contact -> CM ()
|
||||
createFeatureEnabledItems user ct = createFeatureEnabledItems_ user ct >>= toView . CEvtNewChatItems user
|
||||
|
||||
createFeatureEnabledItems_ :: User -> Contact -> CM [AChatItem]
|
||||
createFeatureEnabledItems_ user ct@Contact {mergedPreferences} =
|
||||
forM allChatFeatures $ \(ACF f) -> do
|
||||
let state = featureState $ getContactUserPreference f mergedPreferences
|
||||
createInternalItemForChat user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing
|
||||
|
||||
createFeatureItems ::
|
||||
MsgDirectionI d =>
|
||||
User ->
|
||||
@@ -2337,16 +2353,24 @@ sameGroupProfileInfo :: GroupProfile -> GroupProfile -> Bool
|
||||
sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferences = Nothing}
|
||||
|
||||
createGroupFeatureItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM ()
|
||||
createGroupFeatureItems user cd ciContent GroupInfo {fullGroupPreferences} =
|
||||
forM_ allGroupFeatures $ \(AGF f) -> do
|
||||
createGroupFeatureItems user cd ciContent g = createGroupFeatureItems_ user cd ciContent g >>= toView . CEvtNewChatItems user
|
||||
|
||||
createGroupFeatureItems_ :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM [AChatItem]
|
||||
createGroupFeatureItems_ user cd ciContent GroupInfo {fullGroupPreferences} =
|
||||
forM allGroupFeatures $ \(AGF f) -> do
|
||||
let p = getGroupPreference f fullGroupPreferences
|
||||
(_, param, role) = groupFeatureState p
|
||||
createInternalChatItem user cd (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing
|
||||
createInternalItemForChat user cd (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing
|
||||
|
||||
createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM ()
|
||||
createInternalChatItem user cd content itemTs_ =
|
||||
createInternalChatItem user cd content itemTs_ = do
|
||||
ci <- createInternalItemForChat user cd content itemTs_
|
||||
toView $ CEvtNewChatItems user [ci]
|
||||
|
||||
createInternalItemForChat :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM AChatItem
|
||||
createInternalItemForChat user cd content itemTs_ =
|
||||
lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case
|
||||
[Right aci] -> toView $ CEvtNewChatItems user [aci]
|
||||
[Right ci] -> pure ci
|
||||
[Left e] -> throwError e
|
||||
rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs)
|
||||
|
||||
|
||||
@@ -556,8 +556,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- TODO check member ID
|
||||
-- TODO update member profile
|
||||
pure ()
|
||||
XInfo profile ->
|
||||
void $ processContactProfileUpdate ct profile False
|
||||
XInfo profile -> do
|
||||
let prepared = isJust $ preparedContact ct
|
||||
void $ processContactProfileUpdate ct profile prepared
|
||||
XOk -> pure ()
|
||||
_ -> messageError "INFO for existing contact must have x.grp.mem.info, x.info or x.ok"
|
||||
CON pqEnc ->
|
||||
@@ -570,9 +571,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||
lift $ setContactNetworkStatus ct' NSConnected
|
||||
toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
|
||||
when (directOrUsed ct') $ do
|
||||
createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing
|
||||
createFeatureEnabledItems ct'
|
||||
let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing
|
||||
when (directOrUsed ct') $ case preparedContact ct' of
|
||||
Nothing -> do
|
||||
createE2EItem
|
||||
createFeatureEnabledItems user ct'
|
||||
Just PreparedContact {connLinkToConnect = cl} ->
|
||||
unless (Just pqEnc == connLinkPQEncryption cl) createE2EItem
|
||||
when (contactConnInitiated conn') $ do
|
||||
let Connection {groupLinkId} = conn'
|
||||
doProbeContacts = isJust groupLinkId
|
||||
@@ -799,7 +804,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
toView $ CEvtUserJoinedGroup user gInfo' m'
|
||||
(gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m'
|
||||
let cd = CDGroupRcv gInfo'' scopeInfo m''
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CIRcvGroupFeature gInfo''
|
||||
memberConnectedChatItem gInfo'' scopeInfo m''
|
||||
unless (memberPending membership) $ maybeCreateGroupDescrLocal gInfo'' m''
|
||||
@@ -2181,7 +2186,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- create item in both scopes
|
||||
let gInfo' = gInfo {membership = membership'}
|
||||
cd = CDGroupRcv gInfo' Nothing m
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CIRcvGroupFeature gInfo'
|
||||
maybeCreateGroupDescrLocal gInfo' m
|
||||
createInternalChatItem user cd (CIRcvGroupEvent RGEUserAccepted) Nothing
|
||||
@@ -2250,12 +2255,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
|
||||
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m'') ciContent itemTs_
|
||||
|
||||
createFeatureEnabledItems :: Contact -> CM ()
|
||||
createFeatureEnabledItems ct@Contact {mergedPreferences} =
|
||||
forM_ allChatFeatures $ \(ACF f) -> do
|
||||
let state = featureState $ getContactUserPreference f mergedPreferences
|
||||
createInternalChatItem user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing
|
||||
|
||||
xInfoProbe :: ContactOrMember -> Probe -> CM ()
|
||||
xInfoProbe cgm2 probe = do
|
||||
contactMerge <- readTVarIO =<< asks contactMergeEnabled
|
||||
|
||||
@@ -173,7 +173,7 @@ data CIContent (d :: MsgDirection) where
|
||||
|
||||
deriving instance Show (CIContent d)
|
||||
|
||||
data E2EInfo = E2EInfo {pqEnabled :: PQEncryption}
|
||||
data E2EInfo = E2EInfo {pqEnabled :: Maybe PQEncryption}
|
||||
deriving (Eq, Show)
|
||||
|
||||
ciMsgContent :: CIContent d -> Maybe MsgContent
|
||||
@@ -296,11 +296,17 @@ ciContentToText = \case
|
||||
|
||||
directE2EInfoToText :: E2EInfo -> Text
|
||||
directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
|
||||
PQEncOn -> e2eInfoPQText
|
||||
PQEncOff -> e2eInfoNoPQText
|
||||
Just PQEncOn -> e2eInfoPQText
|
||||
Just PQEncOff -> e2eInfoNoPQText
|
||||
Nothing -> simpleE2EText
|
||||
|
||||
groupE2EInfoToText :: E2EInfo -> Text
|
||||
groupE2EInfoToText _e2eeInfo = e2eInfoNoPQText
|
||||
groupE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
|
||||
Just _ -> e2eInfoNoPQText
|
||||
Nothing -> simpleE2EText
|
||||
|
||||
simpleE2EText :: Text
|
||||
simpleE2EText = "This conversation is protected by end-to-end encryption"
|
||||
|
||||
e2eInfoNoPQText :: Text
|
||||
e2eInfoNoPQText =
|
||||
|
||||
@@ -533,7 +533,16 @@ cmToQuotedMsg = \case
|
||||
ACME _ (XMsgNew (MCQuote quotedMsg _)) -> Just quotedMsg
|
||||
_ -> Nothing
|
||||
|
||||
data MsgContentTag = MCText_ | MCLink_ | MCImage_ | MCVideo_ | MCVoice_ | MCFile_ | MCReport_ | MCUnknown_ Text
|
||||
data MsgContentTag
|
||||
= MCText_
|
||||
| MCLink_
|
||||
| MCImage_
|
||||
| MCVideo_
|
||||
| MCVoice_
|
||||
| MCFile_
|
||||
| MCReport_
|
||||
| MCChat_
|
||||
| MCUnknown_ Text
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance StrEncoding MsgContentTag where
|
||||
@@ -545,6 +554,7 @@ instance StrEncoding MsgContentTag where
|
||||
MCFile_ -> "file"
|
||||
MCVoice_ -> "voice"
|
||||
MCReport_ -> "report"
|
||||
MCChat_ -> "chat"
|
||||
MCUnknown_ t -> encodeUtf8 t
|
||||
strDecode = \case
|
||||
"text" -> Right MCText_
|
||||
@@ -554,6 +564,7 @@ instance StrEncoding MsgContentTag where
|
||||
"voice" -> Right MCVoice_
|
||||
"file" -> Right MCFile_
|
||||
"report" -> Right MCReport_
|
||||
"chat" -> Right MCChat_
|
||||
t -> Right . MCUnknown_ $ safeDecodeUtf8 t
|
||||
strP = strDecode <$?> A.takeTill (== ' ')
|
||||
|
||||
@@ -593,9 +604,16 @@ data MsgContent
|
||||
| MCVoice {text :: Text, duration :: Int}
|
||||
| MCFile {text :: Text}
|
||||
| MCReport {text :: Text, reason :: ReportReason}
|
||||
| MCChat {text :: Text, chatLink :: MsgChatLink}
|
||||
| MCUnknown {tag :: Text, text :: Text, json :: J.Object}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data MsgChatLink
|
||||
= MCLContact {connLink :: ShortLinkContact, profile :: Profile, business :: Bool}
|
||||
| MCLInvitation {invLink :: ShortLinkInvitation, profile :: Profile}
|
||||
| MCLGroup {connLink :: ShortLinkContact, groupProfile :: GroupProfile}
|
||||
deriving (Eq, Show)
|
||||
|
||||
msgContentText :: MsgContent -> Text
|
||||
msgContentText = \case
|
||||
MCText t -> t
|
||||
@@ -611,6 +629,7 @@ msgContentText = \case
|
||||
if T.null text then msg else msg <> ": " <> text
|
||||
where
|
||||
msg = "report " <> safeDecodeUtf8 (strEncode reason)
|
||||
MCChat {text} -> text
|
||||
MCUnknown {text} -> text
|
||||
|
||||
durationText :: Int -> Text
|
||||
@@ -646,6 +665,7 @@ msgContentTag = \case
|
||||
MCVoice {} -> MCVoice_
|
||||
MCFile {} -> MCFile_
|
||||
MCReport {} -> MCReport_
|
||||
MCChat {} -> MCChat_
|
||||
MCUnknown {tag} -> MCUnknown_ tag
|
||||
|
||||
data ExtMsgContent = ExtMsgContent
|
||||
@@ -664,6 +684,8 @@ data ExtMsgContent = ExtMsgContent
|
||||
data MsgMention = MsgMention {memberId :: MemberId}
|
||||
deriving (Eq, Show)
|
||||
|
||||
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "MCL") ''MsgChatLink)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''MsgMention)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''QuotedMsg)
|
||||
@@ -773,6 +795,10 @@ instance FromJSON MsgContent where
|
||||
text <- v .: "text"
|
||||
reason <- v .: "reason"
|
||||
pure MCReport {text, reason}
|
||||
MCChat_ -> do
|
||||
text <- v .: "text"
|
||||
chatLink <- v .: "chatLink"
|
||||
pure MCChat {text, chatLink}
|
||||
MCUnknown_ tag -> do
|
||||
text <- fromMaybe unknownMsgType <$> v .:? "text"
|
||||
pure MCUnknown {tag, text, json = v}
|
||||
@@ -807,6 +833,7 @@ instance ToJSON MsgContent where
|
||||
MCVoice {text, duration} -> J.object ["type" .= MCVoice_, "text" .= text, "duration" .= duration]
|
||||
MCFile t -> J.object ["type" .= MCFile_, "text" .= t]
|
||||
MCReport {text, reason} -> J.object ["type" .= MCReport_, "text" .= text, "reason" .= reason]
|
||||
MCChat {text, chatLink} -> J.object ["type" .= MCChat_, "text" .= text, "chatLink" .= chatLink]
|
||||
toEncoding = \case
|
||||
MCUnknown {json} -> JE.value $ J.Object json
|
||||
MCText t -> J.pairs $ "type" .= MCText_ <> "text" .= t
|
||||
@@ -816,6 +843,7 @@ instance ToJSON MsgContent where
|
||||
MCVoice {text, duration} -> J.pairs $ "type" .= MCVoice_ <> "text" .= text <> "duration" .= duration
|
||||
MCFile t -> J.pairs $ "type" .= MCFile_ <> "text" .= t
|
||||
MCReport {text, reason} -> J.pairs $ "type" .= MCReport_ <> "text" .= text <> "reason" .= reason
|
||||
MCChat {text, chatLink} -> J.pairs $ "type" .= MCChat_ <> "text" .= text <> "chatLink" .= chatLink
|
||||
|
||||
instance ToField MsgContent where
|
||||
toField = toField . encodeJSON
|
||||
|
||||
@@ -125,8 +125,8 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
activeConn = Just conn
|
||||
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
preparedContact = toPreparedContact connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, preparedContact, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
getGroupAndMember_ groupMemberId c = do
|
||||
gm <-
|
||||
@@ -218,7 +218,7 @@ getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap either
|
||||
(cReq, connId) <- ExceptT getConnReqConnId
|
||||
(cReq,) <$> getConnectionEntity db vr user connId
|
||||
where
|
||||
getConnReqConnId =
|
||||
getConnReqConnId =
|
||||
firstRow' toConnReqConnId (SEInternalError "connection not found") $
|
||||
DB.query
|
||||
db
|
||||
|
||||
@@ -104,7 +104,7 @@ import Simplex.Chat.Store.Shared
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink, ConnId, CreatedConnLink (..), InvitationId, UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink (..), ConnId, CreatedConnLink (..), InvitationId, UserId, connMode)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
@@ -263,7 +263,7 @@ createIncognitoProfile db User {userId} p = do
|
||||
createIncognitoProfile_ db userId createdAt p
|
||||
|
||||
createPreparedContact :: DB.Connection -> User -> Profile -> ACreatedConnLink -> ExceptT StoreError IO Contact
|
||||
createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToConnect = do
|
||||
createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToConnect@(ACCL m _) = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
(localDisplayName, contactId, profileId) <- createContact_ db userId p (Just connLinkToConnect) "" Nothing currentTs
|
||||
let profile = toLocalProfile profileId p ""
|
||||
@@ -284,7 +284,7 @@ createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToCo
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Just connLinkToConnect,
|
||||
preparedContact = Just $ PreparedContact connLinkToConnect $ connMode m,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
@@ -347,7 +347,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Nothing,
|
||||
preparedContact = Nothing,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
@@ -931,7 +931,7 @@ createContactFromRequest db user@User {userId, profile = LocalProfile {preferenc
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Nothing,
|
||||
preparedContact = Nothing,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
|
||||
@@ -172,7 +172,7 @@ import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (ACreatedConnLink, ConnId, CreatedConnLink (..), UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), UserId)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
@@ -579,7 +579,7 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {
|
||||
DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId)
|
||||
DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId)
|
||||
|
||||
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> ACreatedConnLink -> ExceptT StoreError IO GroupInfo
|
||||
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> CreatedLinkContact -> ExceptT StoreError IO GroupInfo
|
||||
createPreparedGroup db vr user@User {userId, userContactId} groupProfile connLinkToConnect = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
(groupId, groupLDN) <- createGroup_ db userId groupProfile (Just connLinkToConnect) Nothing currentTs
|
||||
@@ -781,7 +781,7 @@ createGroupViaLink'
|
||||
)
|
||||
insertedRowId db
|
||||
|
||||
createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe ACreatedConnLink -> Maybe BusinessChatInfo -> UTCTime -> ExceptT StoreError IO (GroupId, Text)
|
||||
createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe CreatedLinkContact -> Maybe BusinessChatInfo -> UTCTime -> ExceptT StoreError IO (GroupId, Text)
|
||||
createGroup_ db userId groupProfile connLinkToConnect business currentTs = ExceptT $ do
|
||||
let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile
|
||||
withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do
|
||||
@@ -800,7 +800,7 @@ createGroup_ db userId groupProfile connLinkToConnect business currentTs = Excep
|
||||
business_chat, business_member_id, customer_member_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. connLinkToConnectRow connLinkToConnect :. businessChatInfoRow business)
|
||||
((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. connLinkToConnectRow' connLinkToConnect :. businessChatInfoRow business)
|
||||
groupId <- insertedRowId db
|
||||
pure (groupId, localDisplayName)
|
||||
|
||||
@@ -2525,7 +2525,7 @@ createMemberContact
|
||||
quotaErrCounter = 0
|
||||
}
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
|
||||
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, preparedContact = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
|
||||
getMemberContact :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
|
||||
getMemberContact db vr user contactId = do
|
||||
@@ -2562,7 +2562,7 @@ createMemberContactInvited
|
||||
contactId <- createContactUpdateMember currentTs userPreferences
|
||||
ctConn <- createMemberContactConn_ db user connIds gInfo mConn contactId subMode
|
||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
|
||||
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, preparedContact = Nothing, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
m' = m {memberContactId = Just contactId}
|
||||
pure (mCt', m')
|
||||
where
|
||||
|
||||
@@ -30,6 +30,7 @@ import Data.Maybe (fromMaybe, isJust, listToMaybe)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Time.Clock (UTCTime (..), getCurrentTime)
|
||||
import Data.Type.Equality
|
||||
import Simplex.Chat.Messages
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Remote.Types
|
||||
@@ -37,7 +38,7 @@ import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), AConnShortLink (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionMode (..), CreatedConnLink (..), SConnectionMode (..), UserId)
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), AConnShortLink (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionMode (..), ConnectionRequestUri, CreatedConnLink (..), UserId, connMode)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
|
||||
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
@@ -396,13 +397,21 @@ createContact_ db userId Profile {displayName, fullName, image, contactLink, pre
|
||||
contactId <- insertedRowId db
|
||||
pure $ Right (ldn, contactId, profileId)
|
||||
|
||||
type ConnLinkToConnectRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink)
|
||||
type AConnLinkToConnectRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink)
|
||||
|
||||
connLinkToConnectRow :: Maybe ACreatedConnLink -> ConnLinkToConnectRow
|
||||
connLinkToConnectRow :: Maybe ACreatedConnLink -> AConnLinkToConnectRow
|
||||
connLinkToConnectRow = \case
|
||||
Just (ACCL m (CCLink fullLink shortLink)) -> (Just (ACR m fullLink), ACSL m <$> shortLink)
|
||||
Nothing -> (Nothing, Nothing)
|
||||
|
||||
type ConnLinkToConnectRow m = (Maybe (ConnectionRequestUri m), Maybe (ConnShortLink m))
|
||||
|
||||
connLinkToConnectRow' :: Maybe (CreatedConnLink m) -> ConnLinkToConnectRow m
|
||||
connLinkToConnectRow' = \case
|
||||
Just (CCLink fullLink shortLink) -> (Just fullLink, shortLink)
|
||||
Nothing -> (Nothing, Nothing)
|
||||
{-# INLINE connLinkToConnectRow' #-}
|
||||
|
||||
deleteUnusedIncognitoProfileById_ :: DB.Connection -> User -> ProfileId -> IO ()
|
||||
deleteUnusedIncognitoProfileById_ db User {userId} profileId =
|
||||
DB.execute
|
||||
@@ -432,17 +441,18 @@ toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, via
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
|
||||
incognito = maybe False connIncognito activeConn
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
|
||||
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
preparedContact = toPreparedContact connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, preparedContact, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
|
||||
toPreparedContact :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe PreparedContact
|
||||
toPreparedContact connFullLink connShortLink =
|
||||
(\cl@(ACCL m _) -> PreparedContact cl $ connMode m) <$> toACreatedConnLink_ connFullLink connShortLink
|
||||
|
||||
toACreatedConnLink_ :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe ACreatedConnLink
|
||||
toACreatedConnLink_ connFullLink connShortLink = case (connFullLink, connShortLink) of
|
||||
(Nothing, _) -> Nothing
|
||||
(Just (ACR m cr), Nothing) -> Just $ ACCL m (CCLink cr Nothing)
|
||||
(Just (ACR m cr), Just (ACSL m' l)) -> case (m, m') of
|
||||
(SCMInvitation, SCMInvitation) -> Just $ ACCL SCMInvitation (CCLink cr (Just l))
|
||||
(SCMContact, SCMContact) -> Just $ ACCL SCMContact (CCLink cr (Just l))
|
||||
_ -> Nothing
|
||||
toACreatedConnLink_ Nothing _ = Nothing
|
||||
toACreatedConnLink_ (Just (ACR m cr)) csl = case csl of
|
||||
Nothing -> Just $ ACCL m $ CCLink cr Nothing
|
||||
Just (ACSL m' l) -> (\Refl -> ACCL m $ CCLink cr (Just l)) <$> testEquality m m'
|
||||
|
||||
getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile
|
||||
getProfileById db userId profileId =
|
||||
|
||||
@@ -188,7 +188,7 @@ data Contact = Contact
|
||||
createdAt :: UTCTime,
|
||||
updatedAt :: UTCTime,
|
||||
chatTs :: Maybe UTCTime,
|
||||
connLinkToConnect :: Maybe ACreatedConnLink,
|
||||
preparedContact :: Maybe PreparedContact,
|
||||
contactRequestId :: Maybe Int64,
|
||||
contactGroupMemberId :: Maybe GroupMemberId,
|
||||
contactGrpInvSent :: Bool,
|
||||
@@ -200,6 +200,9 @@ data Contact = Contact
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data PreparedContact = PreparedContact {connLinkToConnect :: ACreatedConnLink, uiConnLinkType :: ConnectionMode}
|
||||
deriving (Eq, Show)
|
||||
|
||||
newtype CustomData = CustomData J.Object
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -658,7 +661,8 @@ deriving newtype instance FromField ImageData
|
||||
|
||||
data ContactShortLinkData = ContactShortLinkData
|
||||
{ profile :: Profile,
|
||||
message :: Maybe Text
|
||||
message :: Maybe Text,
|
||||
business :: Bool
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
@@ -1978,6 +1982,8 @@ $(JQ.deriveJSON defaultJSON ''XFTPSndFile)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''FileTransferMeta)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''PreparedContact)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''LocalFileMeta)
|
||||
|
||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FT") ''FileTransfer)
|
||||
|
||||
@@ -3530,7 +3530,7 @@ testShortLinkAddressChangeAutoReply =
|
||||
bobContactSLinkData <- getTermLine bob
|
||||
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " " <> bobContactSLinkData)
|
||||
bob <## "alice: contact is prepared"
|
||||
bob <# "alice> welcome!"
|
||||
-- bob <# "alice> welcome!" -- this message is not sent as event
|
||||
bob ##> "/_connect contact @2 text hello"
|
||||
bob
|
||||
<### [ "alice: connection started",
|
||||
|
||||
Reference in New Issue
Block a user