mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22c62d544c | |||
| fbe9a4da39 | |||
| 2995a79041 | |||
| da197a35a0 | |||
| 6f8578d0f1 | |||
| 73cabd3c69 | |||
| c30c181209 | |||
| 701b4186b6 | |||
| 257e03f10a | |||
| 7b644c0dcf | |||
| c3d9d9a7c3 | |||
| 261767035e | |||
| c9b00b3054 | |||
| 1697190189 | |||
| 24609a98c6 | |||
| 60752feb9c | |||
| ff5ef638cd | |||
| df619d540b | |||
| 53d8a85b8c |
@@ -22,8 +22,10 @@ struct SimpleXApp: App {
|
||||
|
||||
init() {
|
||||
DispatchQueue.global(qos: .background).sync {
|
||||
haskell_init()
|
||||
// hs_init(0, nil)
|
||||
// we have to use debug profile file name without extension here because .hp extension is added by profiler
|
||||
// haskell_init(0, nil, nil)
|
||||
let dummyHpPath = getAppDebugProfilePrefixPath().path + "_dummy" // not used, but required to populate eventlog
|
||||
haskell_init(0, getAppEventLogPath().path, dummyHpPath)
|
||||
}
|
||||
UserDefaults.standard.register(defaults: appDefaults)
|
||||
setGroupDefaults()
|
||||
|
||||
@@ -70,14 +70,14 @@ struct CIImageView: View {
|
||||
}
|
||||
|
||||
private func imageView(_ img: UIImage) -> some View {
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : img.imageData == nil ? .infinity : maxWidth
|
||||
DispatchQueue.main.async { imgWidth = w }
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
if img.imageData == nil {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.frame(maxWidth: w)
|
||||
} else {
|
||||
SwiftyGif(image: img)
|
||||
.frame(width: w, height: w * img.size.height / img.size.width)
|
||||
|
||||
@@ -243,13 +243,13 @@ struct CIVideoView: View {
|
||||
}
|
||||
|
||||
private func imageView(_ img: UIImage) -> some View {
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth
|
||||
let w = img.size.width <= img.size.height ? maxWidth * 0.75 : .infinity
|
||||
DispatchQueue.main.async { videoWidth = w }
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: w)
|
||||
.frame(maxWidth: w)
|
||||
loadingIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +514,6 @@ struct ChatView: View {
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
itemWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
chatView: self
|
||||
@@ -527,7 +526,6 @@ struct ChatView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var maxWidth: CGFloat
|
||||
@State var itemWidth: CGFloat
|
||||
@Binding var composeState: ComposeState
|
||||
@Binding var selectedMember: GMember?
|
||||
var chatView: ChatView
|
||||
@@ -656,7 +654,7 @@ struct ChatView: View {
|
||||
playbackState: $playbackState,
|
||||
playbackTime: $playbackTime
|
||||
)
|
||||
.uiKitContextMenu(hasImageOrVideo: ci.content.msgContent?.isImageOrVideo == true, maxWidth: maxWidth, itemWidth: $itemWidth, menu: uiMenu, allowMenu: $allowMenu)
|
||||
.uiKitContextMenu(maxWidth: maxWidth, menu: uiMenu, allowMenu: $allowMenu)
|
||||
.accessibilityLabel("")
|
||||
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
|
||||
chatItemReactions(ci)
|
||||
|
||||
@@ -11,20 +11,11 @@ import UIKit
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func uiKitContextMenu(hasImageOrVideo: Bool, maxWidth: CGFloat, itemWidth: Binding<CGFloat>, menu: Binding<UIMenu>, allowMenu: Binding<Bool>) -> some View {
|
||||
func uiKitContextMenu(maxWidth: CGFloat, menu: Binding<UIMenu>, allowMenu: Binding<Bool>) -> some View {
|
||||
Group {
|
||||
if allowMenu.wrappedValue {
|
||||
if hasImageOrVideo {
|
||||
InteractionView(content:
|
||||
self.environmentObject(ChatModel.shared)
|
||||
.overlay(DetermineWidthImageVideoItem())
|
||||
.onPreferenceChange(DetermineWidthImageVideoItem.Key.self) { itemWidth.wrappedValue = $0 == 0 ? maxWidth : $0 }
|
||||
, maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
|
||||
.frame(maxWidth: itemWidth.wrappedValue)
|
||||
} else {
|
||||
InteractionView(content: self.environmentObject(ChatModel.shared), maxWidth: maxWidth, itemWidth: itemWidth, menu: menu)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
}
|
||||
InteractionView(content: self, maxWidth: maxWidth, menu: menu)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
@@ -40,14 +31,13 @@ private class HostingViewHolder: UIView {
|
||||
struct InteractionView<Content: View>: UIViewRepresentable {
|
||||
let content: Content
|
||||
var maxWidth: CGFloat
|
||||
var itemWidth: Binding<CGFloat>
|
||||
@Binding var menu: UIMenu
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
let view = HostingViewHolder()
|
||||
view.contentSize = CGSizeMake(maxWidth, .infinity)
|
||||
view.backgroundColor = .clear
|
||||
let hostView = UIHostingController(rootView: content)
|
||||
view.contentSize = hostView.view.intrinsicContentSize
|
||||
hostView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
let constraints = [
|
||||
hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
@@ -67,11 +57,7 @@ struct InteractionView<Content: View>: UIViewRepresentable {
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIView, context: Context) {
|
||||
let was = (uiView as! HostingViewHolder).contentSize
|
||||
(uiView as! HostingViewHolder).contentSize = uiView.subviews[0].sizeThatFits(CGSizeMake(itemWidth.wrappedValue, .infinity))
|
||||
if was != (uiView as! HostingViewHolder).contentSize {
|
||||
uiView.invalidateIntrinsicContentSize()
|
||||
}
|
||||
(uiView as! HostingViewHolder).contentSize = uiView.subviews[0].sizeThatFits(CGSizeMake(maxWidth, .infinity))
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
|
||||
@@ -21,19 +21,6 @@ struct DetermineWidth: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineWidthImageVideoItem: View {
|
||||
typealias Key = MaximumWidthImageVideoPreferenceKey
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(
|
||||
key: MaximumWidthImageVideoPreferenceKey.self,
|
||||
value: proxy.size.width
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumWidthPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
@@ -41,13 +28,6 @@ struct MaximumWidthPreferenceKey: PreferenceKey {
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumWidthImageVideoPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineWidth_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DetermineWidth()
|
||||
|
||||
@@ -45,6 +45,11 @@ struct DeveloperView: View {
|
||||
}
|
||||
|
||||
if developerTools {
|
||||
Section {
|
||||
exportDebugProfileButton()
|
||||
exportEventLogButton()
|
||||
}
|
||||
|
||||
Section {
|
||||
settingsRow("key") {
|
||||
Toggle("Post-quantum E2EE", isOn: $pqExperimentalEnabled)
|
||||
@@ -62,6 +67,24 @@ struct DeveloperView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func exportDebugProfileButton() -> some View {
|
||||
let url = getAppDebugProfilePath()
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Button("Export debugging profile") {
|
||||
showShareSheet(items: [url])
|
||||
}
|
||||
}.disabled(!FileManager.default.fileExists(atPath: url.path))
|
||||
}
|
||||
|
||||
@ViewBuilder private func exportEventLogButton() -> some View {
|
||||
let url = getAppEventLogPath()
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Button("Export event log") {
|
||||
showShareSheet(items: [url])
|
||||
}
|
||||
}.disabled(!FileManager.default.fileExists(atPath: url.path))
|
||||
}
|
||||
|
||||
private func setPQExperimentalEnabled(_ enable: Bool) {
|
||||
do {
|
||||
try apiSetPQEncryption(enable)
|
||||
|
||||
@@ -65,24 +65,20 @@ class NSEThreads {
|
||||
}
|
||||
|
||||
func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void {
|
||||
var waitTime: Int64 = 5_000_000000
|
||||
while waitTime > 0 {
|
||||
if let (_, nse) = rcvEntityThread(id),
|
||||
nse.shouldProcessNtf && nse.processReceivedNtf(ntf) {
|
||||
var timeoutOfWaiting: Int64 = 5_000_000000
|
||||
while timeoutOfWaiting > 0 {
|
||||
let activeThread = NSEThreads.queue.sync {
|
||||
activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id })
|
||||
}
|
||||
if activeThread?.1.processReceivedNtf?(ntf) == true {
|
||||
break
|
||||
} else {
|
||||
try? await Task.sleep(nanoseconds: 10_000000)
|
||||
waitTime -= 10_000000
|
||||
timeoutOfWaiting -= 10_000000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? {
|
||||
NSEThreads.queue.sync {
|
||||
activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id })
|
||||
}
|
||||
}
|
||||
|
||||
func endThread(_ t: UUID) -> Bool {
|
||||
NSEThreads.queue.sync {
|
||||
let tActive: UUID? = if let index = activeThreads.firstIndex(where: { $0.0 == t }) {
|
||||
@@ -117,11 +113,9 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
// thread is added to allThreads here - if thread did not start chat,
|
||||
// chat does not need to be suspended but NSE state still needs to be set to "suspended".
|
||||
var threadId: UUID? = NSEThreads.shared.newThread()
|
||||
var notificationInfo: NtfMessages?
|
||||
var receiveEntityId: String?
|
||||
var expectedMessages: Set<String> = []
|
||||
// return true if the message is taken - it prevents sending it to another NotificationService instance for processing
|
||||
var shouldProcessNtf = false
|
||||
var processReceivedNtf: ((NSENotification) -> Bool)?
|
||||
var appSubscriber: AppSubscriber?
|
||||
var returnedSuspension = false
|
||||
|
||||
@@ -198,11 +192,39 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
? .nse(createConnectionEventNtf(ntfInfo.user, connEntity))
|
||||
: .empty
|
||||
)
|
||||
if let id = connEntity.id, ntfInfo.msgTs != nil {
|
||||
notificationInfo = ntfInfo
|
||||
if let id = connEntity.id, let msgTs = ntfInfo.msgTs {
|
||||
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||
processReceivedNtf = { ntf in
|
||||
if !ntfInfo.user.showNotifications {
|
||||
self.setBestAttemptNtf(.empty)
|
||||
}
|
||||
if case let .msgInfo(info) = ntf {
|
||||
let found = expected.remove(info.msgId)
|
||||
if found != nil {
|
||||
logger.debug("NotificationService processNtf: msgInfo, last: \(expected.isEmpty)")
|
||||
if expected.isEmpty {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
} else if info.msgTs > msgTs {
|
||||
logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one")
|
||||
self.deliverBestAttemptNtf()
|
||||
return false
|
||||
} else {
|
||||
logger.debug("NotificationService processNtf: unknown message, let other instance to process it")
|
||||
return false
|
||||
}
|
||||
} else if ntfInfo.user.showNotifications {
|
||||
logger.debug("NotificationService processNtf: setting best attempt")
|
||||
self.setBestAttemptNtf(ntf)
|
||||
if ntf.isCallInvitation {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
receiveEntityId = id
|
||||
expectedMessages = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||
shouldProcessNtf = true
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -218,38 +240,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
deliverBestAttemptNtf(urgent: true)
|
||||
}
|
||||
|
||||
func processReceivedNtf(_ ntf: NSENotification) -> Bool {
|
||||
guard let ntfInfo = notificationInfo, let msgTs = ntfInfo.msgTs else { return false }
|
||||
if !ntfInfo.user.showNotifications {
|
||||
self.setBestAttemptNtf(.empty)
|
||||
}
|
||||
if case let .msgInfo(info) = ntf {
|
||||
let found = expectedMessages.remove(info.msgId)
|
||||
if found != nil {
|
||||
logger.debug("NotificationService processNtf: msgInfo, last: \(self.expectedMessages.isEmpty)")
|
||||
if expectedMessages.isEmpty {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
} else if info.msgTs > msgTs {
|
||||
logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one")
|
||||
self.deliverBestAttemptNtf()
|
||||
return false
|
||||
} else {
|
||||
logger.debug("NotificationService processNtf: unknown message, let other instance to process it")
|
||||
return false
|
||||
}
|
||||
} else if ntfInfo.user.showNotifications {
|
||||
logger.debug("NotificationService processNtf: setting best attempt")
|
||||
self.setBestAttemptNtf(ntf)
|
||||
if ntf.isCallInvitation {
|
||||
self.deliverBestAttemptNtf()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setBadgeCount() {
|
||||
badgeCount = ntfBadgeCountGroupDefault.get() + 1
|
||||
ntfBadgeCountGroupDefault.set(badgeCount)
|
||||
@@ -272,7 +262,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private func deliverBestAttemptNtf(urgent: Bool = false) {
|
||||
logger.debug("NotificationService.deliverBestAttemptNtf")
|
||||
// stop processing other messages
|
||||
shouldProcessNtf = false
|
||||
processReceivedNtf = nil
|
||||
|
||||
let suspend: Bool
|
||||
if let t = threadId {
|
||||
@@ -406,7 +396,7 @@ func startChat() -> DBMigrationResult? {
|
||||
|
||||
startLock.wait()
|
||||
defer { startLock.signal() }
|
||||
|
||||
|
||||
if hasChatCtrl() {
|
||||
return switch NSEChatState.shared.value {
|
||||
case .created: doStartChat()
|
||||
@@ -425,7 +415,8 @@ func startChat() -> DBMigrationResult? {
|
||||
|
||||
func doStartChat() -> DBMigrationResult? {
|
||||
logger.debug("NotificationService: doStartChat")
|
||||
haskell_init_nse()
|
||||
haskell_init(1, nil, getAppDebugProfilePrefixPath().path)
|
||||
// haskell_init(1, nil, nil)
|
||||
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
||||
logger.debug("NotificationService: doStartChat \(String(describing: dbStatus))")
|
||||
if dbStatus != .ok {
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */; };
|
||||
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
|
||||
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
|
||||
411D3A7C2BB9601D003D9A22 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 411D3A772BB9601D003D9A22 /* libgmp.a */; };
|
||||
411D3A7D2BB9601D003D9A22 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 411D3A782BB9601D003D9A22 /* libffi.a */; };
|
||||
411D3A7E2BB9601D003D9A22 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 411D3A792BB9601D003D9A22 /* libgmpxx.a */; };
|
||||
411D3A7F2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 411D3A7A2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */; };
|
||||
411D3A802BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 411D3A7B2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */; };
|
||||
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; };
|
||||
5C00168128C4FE760094D739 /* KeyChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00168028C4FE760094D739 /* KeyChain.swift */; };
|
||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; };
|
||||
@@ -111,11 +116,6 @@
|
||||
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
|
||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||
5CC932F52BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC932F02BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a */; };
|
||||
5CC932F62BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC932F12BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a */; };
|
||||
5CC932F72BBDD9FA008A1EB6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC932F22BBDD9F9008A1EB6 /* libgmpxx.a */; };
|
||||
5CC932F82BBDD9FA008A1EB6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC932F32BBDD9F9008A1EB6 /* libgmp.a */; };
|
||||
5CC932F92BBDD9FA008A1EB6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC932F42BBDD9F9008A1EB6 /* libffi.a */; };
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CD67B8E2B0E858A00C510B1 /* hs_init.c */; };
|
||||
@@ -263,6 +263,11 @@
|
||||
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIVideoView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
|
||||
411D3A772BB9601D003D9A22 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = ios/libgmp.a; sourceTree = "<group>"; };
|
||||
411D3A782BB9601D003D9A22 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = ios/libffi.a; sourceTree = "<group>"; };
|
||||
411D3A792BB9601D003D9A22 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = ios/libgmpxx.a; sourceTree = "<group>"; };
|
||||
411D3A7A2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a"; path = "ios/libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
411D3A7B2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a"; path = "ios/libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a"; sourceTree = "<group>"; };
|
||||
5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
|
||||
5C00168028C4FE760094D739 /* KeyChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChain.swift; sourceTree = "<group>"; };
|
||||
5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = "<group>"; };
|
||||
@@ -402,11 +407,6 @@
|
||||
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||
5CC932F02BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5CC932F12BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a"; sourceTree = "<group>"; };
|
||||
5CC932F22BBDD9F9008A1EB6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CC932F32BBDD9F9008A1EB6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CC932F42BBDD9F9008A1EB6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||
5CD67B8E2B0E858A00C510B1 /* hs_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hs_init.c; sourceTree = "<group>"; };
|
||||
@@ -522,12 +522,12 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CC932F72BBDD9FA008A1EB6 /* libgmpxx.a in Frameworks */,
|
||||
5CC932F62BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a in Frameworks */,
|
||||
5CC932F52BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a in Frameworks */,
|
||||
5CC932F92BBDD9FA008A1EB6 /* libffi.a in Frameworks */,
|
||||
5CC932F82BBDD9FA008A1EB6 /* libgmp.a in Frameworks */,
|
||||
411D3A7E2BB9601D003D9A22 /* libgmpxx.a in Frameworks */,
|
||||
411D3A7C2BB9601D003D9A22 /* libgmp.a in Frameworks */,
|
||||
411D3A802BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
411D3A7F2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */,
|
||||
411D3A7D2BB9601D003D9A22 /* libffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -590,11 +590,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CC932F42BBDD9F9008A1EB6 /* libffi.a */,
|
||||
5CC932F32BBDD9F9008A1EB6 /* libgmp.a */,
|
||||
5CC932F22BBDD9F9008A1EB6 /* libgmpxx.a */,
|
||||
5CC932F02BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto-ghc9.6.3.a */,
|
||||
5CC932F12BBDD9F9008A1EB6 /* libHSsimplex-chat-5.6.1.1-KSjuZv7tE7cHRWd28F1Zto.a */,
|
||||
411D3A782BB9601D003D9A22 /* libffi.a */,
|
||||
411D3A772BB9601D003D9A22 /* libgmp.a */,
|
||||
411D3A792BB9601D003D9A22 /* libgmpxx.a */,
|
||||
411D3A7A2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */,
|
||||
411D3A7B2BB9601D003D9A22 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1441,7 +1441,6 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -1503,7 +1502,6 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1532,16 +1530,12 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
@@ -1560,13 +1554,11 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -1581,16 +1573,13 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
ENABLE_PREVIEWS = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
@@ -1609,8 +1598,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1666,15 +1654,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX NSE/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE";
|
||||
@@ -1685,15 +1670,13 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -1703,15 +1686,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "SimpleX NSE/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "SimpleX NSE";
|
||||
@@ -1722,15 +1703,13 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
@@ -1742,17 +1721,14 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@@ -1770,8 +1746,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1780,7 +1755,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -1793,17 +1767,15 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 206;
|
||||
CURRENT_PROJECT_VERSION = 204;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@@ -1821,8 +1793,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6.1;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1831,7 +1802,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = ./SimpleXChat/SimpleX.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
||||
@@ -3209,14 +3209,6 @@ public enum MsgContent: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public var isImageOrVideo: Bool {
|
||||
switch self {
|
||||
case .image: true
|
||||
case .video: true
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
"json \(encodeJSON(self))"
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ func getAppDirectory() -> URL {
|
||||
|
||||
let DB_FILE_PREFIX = "simplex_v1"
|
||||
|
||||
let DEBUG_PROFILE_PREFIX = "simplex_debug"
|
||||
|
||||
let DEBUG_PROFILE_EXTENSION = ".hp"
|
||||
|
||||
func getLegacyDatabasePath() -> URL {
|
||||
getDocumentsDirectory().appendingPathComponent("mobile_v1", isDirectory: false)
|
||||
}
|
||||
@@ -62,6 +66,18 @@ public func getAppDatabasePath() -> URL {
|
||||
: getLegacyDatabasePath()
|
||||
}
|
||||
|
||||
public func getAppDebugProfilePrefixPath() -> URL {
|
||||
getAppDirectory().appendingPathComponent(DEBUG_PROFILE_PREFIX, isDirectory: false)
|
||||
}
|
||||
|
||||
public func getAppDebugProfilePath() -> URL {
|
||||
getAppDirectory().appendingPathComponent(DEBUG_PROFILE_PREFIX + DEBUG_PROFILE_EXTENSION, isDirectory: false)
|
||||
}
|
||||
|
||||
public func getAppEventLogPath() -> URL {
|
||||
getAppDirectory().appendingPathComponent("simplex.eventlog", isDirectory: false)
|
||||
}
|
||||
|
||||
func fileModificationDate(_ path: String) -> Date? {
|
||||
do {
|
||||
let attr = try FileManager.default.attributesOfItem(atPath: path)
|
||||
|
||||
@@ -7,19 +7,53 @@
|
||||
//
|
||||
|
||||
#include "hs_init.h"
|
||||
#include <string.h>
|
||||
|
||||
extern void hs_init_with_rtsopts(int * argc, char **argv[]);
|
||||
|
||||
void haskell_init(void) {
|
||||
int argc = 5;
|
||||
char *argv[] = {
|
||||
"simplex",
|
||||
"+RTS", // requires `hs_init_with_rtsopts`
|
||||
"-A64m", // chunk size for new allocations
|
||||
"-H64m", // initial heap size
|
||||
"-xn", // non-moving GC
|
||||
0
|
||||
};
|
||||
void haskell_init(int nse, const char *eventlog, const char *heap_profile) {
|
||||
// setup static arena for bump allocation and passing to RTS
|
||||
char *argv[32] = {0,};
|
||||
int argc = 0; // number of arguments used so far, always stands at the first NULL in argv
|
||||
// common args
|
||||
if (nse) {
|
||||
argv[argc++] = "simplex-nse"; // fake program name
|
||||
} else {
|
||||
argv[argc++] = "simplex";
|
||||
}
|
||||
argv[argc++] = "+RTS"; // start adding RTS options
|
||||
if (nse) {
|
||||
argv[argc++] = "-S"; // spam stdout with GC stats
|
||||
argv[argc++] = "-A1m"; // chunk size for new allocations (less frequent GC)
|
||||
argv[argc++] = "-H2m"; // larger heap size on start (faster boot)
|
||||
argv[argc++] = "-M12m"; // hard limit on heap
|
||||
argv[argc++] = "-F0.5"; // heap growth triggering GC
|
||||
argv[argc++] = "-Fd1"; // memory return
|
||||
} else {
|
||||
argv[argc++] = "-T"; // make GC counters available from inside the program
|
||||
argv[argc++] = "-A64m"; // chunk size for new allocations (less frequent GC)
|
||||
argv[argc++] = "-H64m"; // larger heap size on start (faster boot)
|
||||
}
|
||||
// argv[argc++] = "-M8G"; // keep memory usage under 8G, collecting more aggressively when approaching it (and crashing sooner rather than taking down the whole system)
|
||||
if (eventlog) {
|
||||
static char ol[1024] = "-ol";
|
||||
(void)strncpy(&ol[3], eventlog, sizeof(ol) - 3);
|
||||
argv[argc++] = ol;
|
||||
argv[argc++] = "-l-agu"; // collect GC and user events
|
||||
}
|
||||
if (heap_profile) {
|
||||
static char po[1024] = "-po";
|
||||
(void)strncpy(&po[3], heap_profile, sizeof(po) - 3);
|
||||
argv[argc++] = po; // adds ".hp" extension
|
||||
argv[argc++] = "-hT"; // emit heap profile by closure type
|
||||
}
|
||||
if (nse) {
|
||||
argv[argc++] = "-c"; // compacting garbage collector
|
||||
} else {
|
||||
int non_moving_gc = !heap_profile; // not compatible with heap profile
|
||||
if (non_moving_gc) argv[argc++] = "-xn";
|
||||
}
|
||||
// wrap args as expected by RTS
|
||||
char **pargv = argv;
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef hs_init_h
|
||||
#define hs_init_h
|
||||
|
||||
void haskell_init(void);
|
||||
void haskell_init(int nse, const char *eventlog, const char *heap_profile);
|
||||
|
||||
void haskell_init_nse(void);
|
||||
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import chat.simplex.common.helpers.toUri
|
||||
import chat.simplex.common.model.CIFile
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.ModalManager
|
||||
@@ -14,6 +15,7 @@ import coil.compose.rememberAsyncImagePainter
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.request.ImageRequest
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
actual fun SimpleAndAnimatedImageView(
|
||||
@@ -41,7 +43,6 @@ actual fun SimpleAndAnimatedImageView(
|
||||
}
|
||||
|
||||
private val imageLoader = ImageLoader.Builder(androidAppContext)
|
||||
.networkObserverEnabled(false)
|
||||
.components {
|
||||
if (SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
|
||||
+13
-14
@@ -3,7 +3,7 @@ package chat.simplex.common.views.chat.item
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
@@ -11,8 +11,8 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.isVisible
|
||||
import chat.simplex.common.helpers.toUri
|
||||
import chat.simplex.common.platform.VideoPlayer
|
||||
import chat.simplex.common.platform.androidAppContext
|
||||
import chat.simplex.res.MR
|
||||
import coil.ImageLoader
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
@@ -23,11 +23,21 @@ import coil.size.Size
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
actual fun FullScreenImageView(modifier: Modifier, data: ByteArray, imageBitmap: ImageBitmap) {
|
||||
// I'm using a new private instance of imageLoader here because if I use one instance in multiple places
|
||||
// I'm making a new instance of imageLoader here because if I use one instance in multiple places
|
||||
// after end of composition here a GIF from the first instance will be paused automatically which isn't what I want
|
||||
val imageLoader = ImageLoader.Builder(LocalContext.current)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
Image(
|
||||
rememberAsyncImagePainter(
|
||||
ImageRequest.Builder(LocalContext.current).data(data = data).size(Size.ORIGINAL).build(),
|
||||
@@ -63,14 +73,3 @@ actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier, close: (
|
||||
modifier
|
||||
)
|
||||
}
|
||||
|
||||
private val imageLoader = ImageLoader.Builder(androidAppContext)
|
||||
.networkObserverEnabled(false)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
+2
-3
@@ -1392,12 +1392,11 @@ private fun providerForGallery(
|
||||
return null
|
||||
}
|
||||
|
||||
// Pager has a bug with overflowing when total pages is around Int.MAX_VALUE. Using smaller value
|
||||
var initialIndex = 10000 / 2
|
||||
var initialIndex = Int.MAX_VALUE / 2
|
||||
var initialChatId = cItemId
|
||||
return object: ImageGalleryProvider {
|
||||
override val initialIndex: Int = initialIndex
|
||||
override val totalMediaSize = mutableStateOf(10000)
|
||||
override val totalMediaSize = mutableStateOf(Int.MAX_VALUE)
|
||||
override fun getMedia(index: Int): ProviderMedia? {
|
||||
val internalIndex = initialIndex - index
|
||||
val item = item(internalIndex, initialChatId)?.second ?: return null
|
||||
|
||||
+6
-20
@@ -38,7 +38,9 @@ import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Composable
|
||||
fun ConnectMobileView() {
|
||||
@@ -267,20 +269,12 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, c
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var userChangedAddress by rememberSaveable { mutableStateOf(false) }
|
||||
var userChangedPort by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
if (customAddress.value != cachedR.address && cachedR != null) {
|
||||
userChangedAddress = true
|
||||
}
|
||||
if (customPort.value != cachedR.port && cachedR != null) {
|
||||
userChangedPort = true
|
||||
}
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = null,
|
||||
multicast = controller.appPrefs.offerRemoteMulticast.get(),
|
||||
address = if (customAddress.value != null && userChangedAddress) customAddress.value else cachedR.rh?.bindAddress_,
|
||||
port = if (customPort.value != null && userChangedPort) customPort.value else cachedR.rh?.bindPort_
|
||||
address = if (customAddress.value?.address != cachedR.address?.address) customAddress.value else cachedR.rh?.bindAddress_,
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR = r
|
||||
@@ -349,20 +343,12 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var userChangedAddress by rememberSaveable { mutableStateOf(false) }
|
||||
var userChangedPort by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
if (customAddress.value != cachedR.address && cachedR != null) {
|
||||
userChangedAddress = true
|
||||
}
|
||||
if (customPort.value != cachedR.port && cachedR != null) {
|
||||
userChangedPort = true
|
||||
}
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = rh.remoteHostId,
|
||||
multicast = controller.appPrefs.offerRemoteMulticast.get(),
|
||||
address = if (customAddress.value != null && userChangedAddress) customAddress.value else cachedR.rh?.bindAddress_ ?: rh.bindAddress_,
|
||||
port = if (customPort.value != null && userChangedPort) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
|
||||
address = if (customAddress.value?.address != cachedR.address?.address) customAddress.value else cachedR.rh?.bindAddress_ ?: rh.bindAddress_,
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR = r
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="121 0 40 40"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="m 126.52238,11.425398 5.80302,5.716401 5.88962,-5.889626 2.8582,2.858201 L 135.1836,20 l 5.7164,5.716402 -2.94482,2.8582 -5.7164,-5.629789 -5.88962,5.803014 -2.8582,-2.858201 5.88962,-5.803014 -5.803,-5.716402 z"
|
||||
fill="#030749"
|
||||
id="path1"
|
||||
style="stroke-width:0.866122" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="m 137.86858,28.661214 2.94481,-2.944812 v 0 l 5.88963,-5.803014 -5.80302,-5.62979 v 0 l -2.8582,-2.8582 -5.7164,-5.7164023 2.94481,-2.9448129 5.7164,5.7164017 5.88963,-5.8030138 2.8582,2.8582008 -5.88962,5.8030145 5.7164,5.716401 5.88962,-5.803014 2.8582,2.858201 -5.88962,5.803014 5.803,5.716402 -2.9448,2.858201 -5.7164,-5.716402 -5.88963,5.803013 5.7164,5.716402 -2.8582,2.944813 -5.80301,-5.716402 -5.80302,5.803015 -2.8582,-2.858201 z"
|
||||
fill="url(#paint0_linear_40_164)"
|
||||
id="path2"
|
||||
style="fill:url(#paint0_linear_40_164);stroke-width:0.866122" />
|
||||
<defs
|
||||
id="defs3">
|
||||
<linearGradient
|
||||
x1="135.948"
|
||||
y1="-0.81632602"
|
||||
x2="132.09599"
|
||||
y2="36.985699"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="paint0_linear_40_164"
|
||||
gradientTransform="matrix(0.86612147,0,0,0.86612147,18.863485,2.6775707)">
|
||||
<stop
|
||||
stop-color="#01f1ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#0197ff"
|
||||
id="stop3" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -21,7 +21,6 @@ import chat.simplex.common.ui.theme.SimpleXTheme
|
||||
import chat.simplex.common.views.TerminalView
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import java.awt.event.WindowEvent
|
||||
@@ -104,7 +103,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
|
||||
simplexWindowState.windowState = windowState
|
||||
// Reload all strings in all @Composable's after language change at runtime
|
||||
if (remember { ChatController.appPrefs.appLanguage.state }.value != "") {
|
||||
Window(state = windowState, icon = painterResource(MR.images.ic_simplex), onCloseRequest = { closedByError.value = false; exitApplication() }, onKeyEvent = {
|
||||
Window(state = windowState, onCloseRequest = { closedByError.value = false; exitApplication() }, onKeyEvent = {
|
||||
if (it.key == Key.Escape && it.type == KeyEventType.KeyUp) {
|
||||
simplexWindowState.backstack.lastOrNull()?.invoke() != null
|
||||
} else {
|
||||
|
||||
+8
-11
@@ -7,8 +7,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.simplexWindowState
|
||||
import java.awt.Window
|
||||
|
||||
@Composable
|
||||
actual fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) {
|
||||
@@ -25,15 +23,14 @@ actual fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLon
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function doesn't take into account multi-window environment. In case more windows will be used, modify the code
|
||||
* */
|
||||
@Composable
|
||||
actual fun LocalWindowWidth(): Dp = with(LocalDensity.current) {
|
||||
val windows = java.awt.Window.getWindows()
|
||||
if (windows.size == 1) {
|
||||
(windows.getOrNull(0)?.width ?: 0).toDp()
|
||||
} else {
|
||||
simplexWindowState.windowState.size.width
|
||||
actual fun LocalWindowWidth(): Dp {
|
||||
return with(LocalDensity.current) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() }
|
||||
/*val density = LocalDensity.current
|
||||
var width by remember { mutableStateOf(with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() }) }
|
||||
SideEffect {
|
||||
if (width != with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() })
|
||||
width = with(density) { (java.awt.Window.getWindows().find { it.isActive }?.width ?: 0).toDp() }
|
||||
}
|
||||
return width.also { println("LALAL $it") }*/
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
|
||||
android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
|
||||
android.version_name=5.6.1
|
||||
android.version_code=193
|
||||
android.version_name=5.6
|
||||
android.version_code=191
|
||||
|
||||
desktop.version_name=5.6.1
|
||||
desktop.version_code=36
|
||||
desktop.version_name=5.6
|
||||
desktop.version_code=35
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
+7
-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: 6bc4f6c94e11f59604b0d9c576e62e01bc08b4cd
|
||||
tag: 8b94264623eb8f32793a3b4129cb12770f54ef3a
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
@@ -34,6 +34,12 @@ source-repository-package
|
||||
location: https://github.com/simplex-chat/aeson.git
|
||||
tag: aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b
|
||||
|
||||
-- old bs/text compat for 8.10
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/base64.git
|
||||
tag: 2d77b6dbcaffc00570a70be8694049f3710e7c94
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/haskell-terminal.git
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# Large public grups / channels
|
||||
|
||||
## Background
|
||||
|
||||
SimpleX Chat users participate in public groups that were created for small, fully connected p2p groups - working groups, teams, etc. The ability to join the groups via the links was added as an afterthought, without forward looking design, simply to accomodate the interest from the users to use SimpleX platform for public groups and communities. Overall, it's correct to say that the emergence of public groups was unexpected in the context of private messaging, and it shows that protecting participants and publishers identity, and having per-group identity is important for many people.
|
||||
|
||||
## Problems of the current p2p design
|
||||
|
||||
### It doesn't scale to large size
|
||||
|
||||
Current design assumes that each peer is connected to each peer and sends messages to all. It creates non-trivial cost of establishing the connections as the group grows, some abandoned connections when some members remain in "connecting" state and also linearly growing traffic to send each message.
|
||||
|
||||
Historically, there were p2p designs when peers connected not to all but some members, but they were mostly used by desktop clients with more persistent network connections, did not provide any asynchronous delivery and were trading lower traffic for latency and availability. Such designs were viable for file sharing across desktop devices, but it probably would not work well for dynamic real-time communities with active participation from a small share of members and the design of other members to observe the conversation as it happens.
|
||||
|
||||
### It doesn't account for participation asymmetry
|
||||
|
||||
Most members of large public groups do not send messages, so connecting them to all other members directly appears unnecessary and costly, and it also requires tracking when members became inactive to stop sending messages to them.
|
||||
|
||||
## Objectives for the new design
|
||||
|
||||
### Transcript integrity
|
||||
|
||||
This issue is covered in detail in [Group integrity](./2023-10-20-group-integrity.md).
|
||||
|
||||
### Asynchronous delivery to group
|
||||
|
||||
Asynchronous delivery is important to protect participants privacy from traffic observation. In addition to that, sending scheduled posts is quite often a convenient feature to schedule multiple updates for a longer period of time - for example, schedule daily updates for a week, doing it once a week.
|
||||
|
||||
### Ability to conceal members list
|
||||
|
||||
This is a rather common request for the current groups, and while it could be possible of course to hide it on the client level, this still makes data available via the database, and puts less technical users in unfair position, while not protecting users privacy from technically competent members.
|
||||
|
||||
### Support pre-moderation
|
||||
|
||||
As the group size grows, so does the activity of the bad actors. Some groups will benefit from switching all, some, most, or new members to pre-moderation - when each post needs to be approved by admin before it becomes visible to all members. It would slow down the conversations, but it would allow a better content quality and owners' control of the content.
|
||||
|
||||
## Channels based on super-peers
|
||||
|
||||
The proposal is to model the new UX design from Telegram channels, with optional subchannels, and granular participation rights for the members / followers.
|
||||
|
||||
Another consideration is to create democratically governed communities when creators don't own the community but only appoint the initial administrators, but as the community grows it can elect the new admins or moderators from the existing members, where voting power is somehow determined by the community score (which is necessary to compensate for anonymous participants who could subvert the vote if plain vote count was made). This is probably out of scope for the initial implementation, but this idea is very appealing and it doesn't exist in any other decentralised platforms.
|
||||
|
||||
Technologically, the channel or group would determine which super-peers would host the group or channel, with group content being a merkle tree with ability to remove some content creating holes - which seems to be very important quality, both to remove undesirable content and to protect participants privacy.
|
||||
|
||||
Super-peer would manage this merkle-tree state based on the messages from owners, admins and members, with the ability to make some destructive actions confirmed by more than one command. E.g., group/channel deletion may require at least 2 or 3 votes (respectively, for 3 and 5 owners), thus protecting both from accidental deletions and from attacks via owner - one of the owners being compromised won't result in group deletion if 2 votes are required. As the group size grows, owners can also modify rules (which in itself can also require m of n votes).
|
||||
|
||||
## Joining group
|
||||
|
||||
The current model when the link to join the group is, effectively, an address of one of the admins, is not censorship-resistant, reliable or convenient - the admin can be offline, be removed, etc. So we want to somehow include addresses of multiple super-peers to join the group. Without identity-layer, the addresses are quite large already, and including multiple addresses in one link, while possible, would make the qr code very hard to scan. Practically, without creating identity layer, we can use up to 2-3 super-peer addresses for the group, and increase it later. 2 addresses is likely to be satisfactory, as one of them could be super-peer hosted by SimpleX Chat, and another - by group owners.
|
||||
|
||||
The client then will be connecting to all super-peers in the address. Once connected, these super-peers could send the addresses of the additional super-peers, but this is probably unnecessary for the initial release.
|
||||
|
||||
## MVP
|
||||
|
||||
The challenge is to decide what should be in scope for the initial release, to make it a valuable upgrade and a viable starting point, without overloading it with the functions that can be added later.
|
||||
|
||||
MVP scope:
|
||||
|
||||
- Core functioning of the group - creation/deletion/choosing and changing super-peers. While a large scope, it appears essential.
|
||||
- Message delivery via super-peers.
|
||||
- Super-peer protocol extension. Most likely super-peer would receive ordinary chat messages, but some operations should be added and require additional protocol messages - adding/removing super-peers to groups.
|
||||
- Protocol extensions for owner actions with approvals - we already had several accidental deletions or lost owner accounts. Possibly, it is out of MVP scope.
|
||||
- Search and history navigation. Current decision to send 100 messages both creates unnecessary traffic spikes, and also doesn't provide access to older history and search functions. But, possibly, it should also be in follow up improvements, and only should be included as the initial protocol design.
|
||||
- New format of the group address to include more than one super-peer.
|
||||
- Granular permissions and management model. While the user interface can evolve, the protocol and the scenarios, and also rules models seems better to be added from the beginning.
|
||||
|
||||
Follow-up / improvements:
|
||||
- More scalable client - we already observe scalability issues with directory service, so replacing SQLite with Postgres, if the group participation starts growing seems very important.
|
||||
|
||||
Out of scope:
|
||||
- additional super-peers.
|
||||
- smart-contacts. While very tempting to generalise permissions and management model via smart contracts, that would radically increase complexity and delivery time.
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.6.1.1
|
||||
version: 5.6.0.4
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
@@ -18,7 +18,6 @@ dependencies:
|
||||
- async == 2.2.*
|
||||
- attoparsec == 0.14.*
|
||||
- base >= 4.7 && < 5
|
||||
- base64-bytestring >= 1.0 && < 1.3
|
||||
- composition == 1.0.*
|
||||
- constraints >= 0.12 && < 0.14
|
||||
- containers == 0.6.*
|
||||
@@ -159,6 +158,8 @@ ghc-options:
|
||||
- -Wincomplete-record-updates
|
||||
- -Wincomplete-uni-patterns
|
||||
- -Wunused-type-patterns
|
||||
- -finfo-table-map
|
||||
- -fdistinct-constructor-tables
|
||||
|
||||
default-extensions:
|
||||
- StrictData
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."6bc4f6c94e11f59604b0d9c576e62e01bc08b4cd" = "08l00ay1ibz7skhlpfjp6z2821zpfd0kxplwdm6zc63m2f6za7cv";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."8b94264623eb8f32793a3b4129cb12770f54ef3a" = "0n4wwpkj9dps7ansvlrdf4kiwjczij7y79i4cnr3gr0lmml8bpbs";
|
||||
"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";
|
||||
"https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr";
|
||||
"https://github.com/simplex-chat/base64.git"."2d77b6dbcaffc00570a70be8694049f3710e7c94" = "0zdskk67fzqrrx1i29s3shp7fh9c0krmq5h6hq03qx0n3xy2m44b";
|
||||
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
||||
"https://github.com/simplex-chat/android-support.git"."9aa09f148089d6752ce563b14c2df1895718d806" = "0pbf2pf13v2kjzi397nr13f1h3jv0imvsq8rpiyy2qyx5vd50pqn";
|
||||
"https://github.com/simplex-chat/zip.git"."bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc" = "1csqfjhvc8wb5h4kxxndmb6iw7b4ib9ff2n81hrizsmnf45a6gg0";
|
||||
|
||||
+8
-15
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.6.1.1
|
||||
version: 5.6.0.4
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -183,14 +183,13 @@ library
|
||||
src
|
||||
default-extensions:
|
||||
StrictData
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -245,14 +244,13 @@ executable simplex-bot
|
||||
apps/simplex-bot
|
||||
default-extensions:
|
||||
StrictData
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -308,14 +306,13 @@ executable simplex-bot-advanced
|
||||
apps/simplex-bot-advanced
|
||||
default-extensions:
|
||||
StrictData
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -374,14 +371,13 @@ executable simplex-broadcast-bot
|
||||
Broadcast.Bot
|
||||
Broadcast.Options
|
||||
Paths_simplex_chat
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -438,14 +434,13 @@ executable simplex-chat
|
||||
apps/simplex-chat
|
||||
default-extensions:
|
||||
StrictData
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -508,14 +503,13 @@ executable simplex-directory-service
|
||||
Directory.Service
|
||||
Directory.Store
|
||||
Paths_simplex_chat
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
aeson ==2.2.*
|
||||
, ansi-terminal >=0.10 && <0.12
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
@@ -602,7 +596,7 @@ test-suite simplex-chat-test
|
||||
apps/simplex-directory-service/src
|
||||
default-extensions:
|
||||
StrictData
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -finfo-table-map -fdistinct-constructor-tables -threaded
|
||||
build-depends:
|
||||
QuickCheck ==2.14.*
|
||||
, aeson ==2.2.*
|
||||
@@ -610,7 +604,6 @@ test-suite simplex-chat-test
|
||||
, async ==2.2.*
|
||||
, attoparsec ==0.14.*
|
||||
, base >=4.7 && <5
|
||||
, base64-bytestring >=1.0 && <1.3
|
||||
, composition ==1.0.*
|
||||
, constraints >=0.12 && <0.14
|
||||
, containers ==0.6.*
|
||||
|
||||
+5
-5
@@ -29,7 +29,6 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import Data.Bifunctor (bimap, first, second)
|
||||
import Data.ByteArray (ScrubbedBytes)
|
||||
import qualified Data.ByteArray as BA
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
@@ -104,8 +103,9 @@ import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKNoPQ, pattern IKPQOff, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn)
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.Base64 (base64P)
|
||||
import qualified Simplex.Messaging.Encoding.Base64 as B64
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (base64P)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
@@ -2989,9 +2989,9 @@ agentSubscriber :: CM' ()
|
||||
agentSubscriber = do
|
||||
q <- asks $ subQ . smpAgent
|
||||
l <- asks chatLock
|
||||
forever $ atomically (readTBQueue q) >>= process l
|
||||
forever $ atomically (readTBQueue q) >>= void . process l
|
||||
where
|
||||
process :: Lock -> (ACorrId, EntityId, APartyCmd 'Agent) -> CM' ()
|
||||
process :: Lock -> (ACorrId, EntityId, APartyCmd 'Agent) -> CM' (Either ChatError ())
|
||||
process l (corrId, entId, APC e msg) = run $ case e of
|
||||
SAENone -> processAgentMessageNoConn msg
|
||||
SAEConn -> processAgentMessage corrId entId msg
|
||||
@@ -3000,7 +3000,7 @@ agentSubscriber = do
|
||||
where
|
||||
run action = do
|
||||
let name = "agentSubscriber entity=" <> show e <> " entId=" <> str entId <> " msg=" <> str (aCommandTag msg)
|
||||
withLock' l name $ action `catchChatError'` (toView' . CRChatError Nothing)
|
||||
withLock l name $ runExceptT $ action `catchChatError` (toView . CRChatError Nothing)
|
||||
str :: StrEncoding a => a -> String
|
||||
str = B.unpack . strEncode
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ import Simplex.Chat.Remote.Types
|
||||
import Simplex.Chat.Store (AutoAccept, StoreError (..), UserContactLink, UserMsgReceiptSettings)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Util (liftIOEither)
|
||||
import Simplex.FileTransfer.Description (FileDescriptionURI)
|
||||
import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo)
|
||||
import Simplex.Messaging.Agent.Client (AgentLocks, AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure)
|
||||
@@ -1341,7 +1340,7 @@ withStore' action = withStore $ liftIO . action
|
||||
withStore :: (DB.Connection -> ExceptT StoreError IO a) -> CM a
|
||||
withStore action = do
|
||||
ChatController {chatStore} <- ask
|
||||
liftIOEither $ withTransaction chatStore (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors
|
||||
liftEither =<< liftIO (withTransaction chatStore (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors)
|
||||
|
||||
withStoreBatch :: Traversable t => (DB.Connection -> t (IO (Either ChatError a))) -> CM' (t (Either ChatError a))
|
||||
withStoreBatch actions = do
|
||||
|
||||
@@ -24,7 +24,6 @@ import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Encoding as JE
|
||||
import qualified Data.Aeson.TH as JQ
|
||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.Char (isSpace)
|
||||
import Data.Int (Int64)
|
||||
@@ -48,6 +47,7 @@ import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..))
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import qualified Simplex.Messaging.Encoding.Base64 as B64
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (MsgBody)
|
||||
|
||||
@@ -17,7 +17,6 @@ import qualified Data.Aeson.TH as JQ
|
||||
import Data.Bifunctor (first)
|
||||
import Data.ByteArray (ScrubbedBytes)
|
||||
import qualified Data.ByteArray as BA
|
||||
import qualified Data.ByteString.Base64.URL as U
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
@@ -50,6 +49,7 @@ import Simplex.Messaging.Agent.Env.SQLite (createAgentStore)
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, closeSQLiteStore, reopenSQLiteStore)
|
||||
import Simplex.Messaging.Client (defaultNetworkConfig)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Encoding.Base64.URL as U
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), CorrId (..), ProtoServerWithAuth (..), ProtocolServer (..))
|
||||
|
||||
@@ -17,7 +17,6 @@ import Data.Bifunctor (bimap)
|
||||
import qualified Data.ByteArray as BA
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Base64.URL as U
|
||||
import Data.Either (fromLeft)
|
||||
import Data.Word (Word8)
|
||||
import Foreign.C (CInt, CString, newCAString)
|
||||
@@ -26,6 +25,7 @@ import Foreign.StablePtr
|
||||
import Simplex.Chat.Controller (ChatController (..))
|
||||
import Simplex.Chat.Mobile.Shared
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Encoding.Base64.URL as U
|
||||
import UnliftIO (atomically)
|
||||
|
||||
cChatEncryptMedia :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CString
|
||||
|
||||
+22
-24
@@ -22,7 +22,6 @@ import Crypto.Random (getRandomBytes)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Types as JT
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Base64.URL as B64U
|
||||
import Data.ByteString.Builder (Builder)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Functor (($>))
|
||||
@@ -56,6 +55,7 @@ import Simplex.Messaging.Agent
|
||||
import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP))
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import qualified Simplex.Messaging.Encoding.Base64.URL as B64U
|
||||
import Simplex.Messaging.Encoding.String (StrEncoding (..))
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport (TLS, closeConnection, tlsUniq)
|
||||
@@ -353,7 +353,7 @@ storeRemoteFile rhId encrypted_ localPath = do
|
||||
createDirectoryIfMissing True tmpDir
|
||||
tmpFile <- liftIO $ tmpDir `uniqueCombine` takeFileName localPath
|
||||
cfArgs <- atomically . CF.randomArgs =<< asks random
|
||||
liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs
|
||||
liftEither =<< liftIO (runExceptT $ withExceptT (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs)
|
||||
pure $ CryptoFile tmpFile $ Just cfArgs
|
||||
|
||||
getRemoteFile :: RemoteHostId -> RemoteFile -> CM ()
|
||||
@@ -493,14 +493,14 @@ parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo
|
||||
parseCtrlAppInfo ctrlAppInfo = do
|
||||
liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo
|
||||
|
||||
handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> CM' ()
|
||||
handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> CM ()
|
||||
handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do
|
||||
logDebug "handleRemoteCommand"
|
||||
liftIO (tryRemoteError' parseRequest) >>= \case
|
||||
liftRC (tryRemoteError parseRequest) >>= \case
|
||||
Right (getNext, rc) -> do
|
||||
chatReadVar' currentUser >>= \case
|
||||
chatReadVar currentUser >>= \case
|
||||
Nothing -> replyError $ ChatError CENoActiveUser
|
||||
Just user -> processCommand user getNext rc `catchChatError'` replyError
|
||||
Just user -> processCommand user getNext rc `catchChatError` replyError
|
||||
Left e -> reply $ RRProtocolError e
|
||||
where
|
||||
parseRequest :: ExceptT RemoteProtocolError IO (GetChunk, RemoteCommand)
|
||||
@@ -510,20 +510,19 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque
|
||||
replyError = reply . RRChatResponse . CRChatCmdError Nothing
|
||||
processCommand :: User -> GetChunk -> RemoteCommand -> CM ()
|
||||
processCommand user getNext = \case
|
||||
RCSend {command} -> lift $ handleSend execChatCommand command >>= reply
|
||||
RCRecv {wait = time} -> lift $ liftIO (handleRecv time remoteOutputQ) >>= reply
|
||||
RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply
|
||||
RCSend {command} -> lift (handleSend execChatCommand command) >>= reply
|
||||
RCRecv {wait = time} -> liftIO (handleRecv time remoteOutputQ) >>= reply
|
||||
RCStoreFile {fileName, fileSize, fileDigest} -> lift (handleStoreFile encryption fileName fileSize fileDigest getNext) >>= reply
|
||||
RCGetFile {file} -> handleGetFile encryption user file replyWith
|
||||
reply :: RemoteResponse -> CM' ()
|
||||
reply :: RemoteResponse -> CM ()
|
||||
reply = (`replyWith` \_ -> pure ())
|
||||
replyWith :: Respond
|
||||
replyWith rr attach =
|
||||
liftIO (tryRemoteError' . encryptEncodeHTTP2Body encryption $ J.encode rr) >>= \case
|
||||
Right resp -> liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do
|
||||
send resp
|
||||
attach send
|
||||
flush
|
||||
Left e -> toView' . CRChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e
|
||||
replyWith rr attach = do
|
||||
resp <- liftRC $ encryptEncodeHTTP2Body encryption $ J.encode rr
|
||||
liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do
|
||||
send resp
|
||||
attach send
|
||||
flush
|
||||
|
||||
takeRCStep :: RCStepTMVar a -> CM a
|
||||
takeRCStep = liftError' (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar
|
||||
@@ -532,7 +531,7 @@ type GetChunk = Int -> IO ByteString
|
||||
|
||||
type SendChunk = Builder -> IO ()
|
||||
|
||||
type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM' ()
|
||||
type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM ()
|
||||
|
||||
liftRC :: ExceptT RemoteProtocolError IO a -> CM a
|
||||
liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError)
|
||||
@@ -550,7 +549,7 @@ handleSend execChatCommand command = do
|
||||
logDebug $ "Send: " <> tshow command
|
||||
-- execChatCommand checks for remote-allowed commands
|
||||
-- convert errors thrown in execChatCommand into error responses to prevent aborting the protocol wrapper
|
||||
RRChatResponse <$> execChatCommand (encodeUtf8 command)
|
||||
RRChatResponse <$> lift (execChatCommand $ encodeUtf8 command) `catchChatError'` (pure . CRChatError Nothing)
|
||||
|
||||
handleRecv :: Int -> TBQueue ChatResponse -> IO RemoteResponse
|
||||
handleRecv time events = do
|
||||
@@ -582,11 +581,11 @@ handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileI
|
||||
cf <- getLocalCryptoFile db commandUserId fileId sent
|
||||
unless (cf == cf') $ throwError $ SEFileNotFound fileId
|
||||
liftRC (tryRemoteError $ getFileInfo path) >>= \case
|
||||
Left e -> lift $ reply (RRProtocolError e) $ \_ -> pure ()
|
||||
Left e -> reply (RRProtocolError e) $ \_ -> pure ()
|
||||
Right (fileSize, fileDigest) ->
|
||||
ExceptT . withFile path ReadMode $ \h -> runExceptT $ do
|
||||
withFile path ReadMode $ \h -> do
|
||||
encFile <- liftRC $ prepareEncryptedFile encryption (h, fileSize)
|
||||
lift $ reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile
|
||||
reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile
|
||||
|
||||
listRemoteCtrls :: CM [RemoteCtrlInfo]
|
||||
listRemoteCtrls = do
|
||||
@@ -624,8 +623,7 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do
|
||||
rc@RemoteCtrl {remoteCtrlId} <- upsertRemoteCtrl ctrlName rcCtrlPairing
|
||||
remoteOutputQ <- asks (tbqSize . config) >>= newTBQueueIO
|
||||
encryption <- mkCtrlRemoteCrypto sessionKeys $ tlsUniq tls
|
||||
cc <- ask
|
||||
http2Server <- liftIO . async $ attachHTTP2Server tls $ \req -> handleRemoteCommand execChatCommand encryption remoteOutputQ req `runReaderT` cc
|
||||
http2Server <- async $ attachHTTP2Server tls $ handleRemoteCommand execChatCommand encryption remoteOutputQ
|
||||
void . forkIO $ monitor sseq http2Server
|
||||
updateRemoteCtrlSession sseq $ \case
|
||||
RCSessionPendingConfirmation {} -> Right RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ}
|
||||
|
||||
@@ -13,17 +13,19 @@ import Simplex.Messaging.Transport.HTTP2 (defaultHTTP2BufferSize, getHTTP2Body)
|
||||
import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2ClientError (..), attachHTTP2Client, bodyHeadSize, connTimeout, defaultHTTP2ClientConfig)
|
||||
import Simplex.Messaging.Transport.HTTP2.Server (HTTP2Request (..), runHTTP2ServerWith)
|
||||
import Simplex.RemoteControl.Discovery
|
||||
import UnliftIO
|
||||
|
||||
attachRevHTTP2Client :: IO () -> TLS -> IO (Either HTTP2ClientError HTTP2Client)
|
||||
attachRevHTTP2Client disconnected = attachHTTP2Client config ANY_ADDR_V4 "0" disconnected defaultHTTP2BufferSize
|
||||
where
|
||||
config = defaultHTTP2ClientConfig {bodyHeadSize = doNotPrefetchHead, connTimeout = maxBound}
|
||||
|
||||
attachHTTP2Server :: TLS -> (HTTP2Request -> IO ()) -> IO ()
|
||||
attachHTTP2Server tls processRequest =
|
||||
runHTTP2ServerWith defaultHTTP2BufferSize ($ tls) $ \sessionId r sendResponse -> do
|
||||
reqBody <- getHTTP2Body r doNotPrefetchHead
|
||||
processRequest HTTP2Request {sessionId, request = r, reqBody, sendResponse}
|
||||
attachHTTP2Server :: MonadUnliftIO m => TLS -> (HTTP2Request -> m ()) -> m ()
|
||||
attachHTTP2Server tls processRequest = do
|
||||
withRunInIO $ \unlift ->
|
||||
runHTTP2ServerWith defaultHTTP2BufferSize ($ tls) $ \sessionId r sendResponse -> do
|
||||
reqBody <- getHTTP2Body r doNotPrefetchHead
|
||||
unlift $ processRequest HTTP2Request {sessionId, request = r, reqBody, sendResponse}
|
||||
|
||||
-- | Suppress storing initial chunk in bodyHead, forcing clients and servers to stream chunks
|
||||
doNotPrefetchHead :: Int
|
||||
|
||||
@@ -18,7 +18,6 @@ import Control.Monad.Except
|
||||
import Control.Monad.IO.Class
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import qualified Data.Aeson.TH as J
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Data.Maybe (fromMaybe, isJust, listToMaybe)
|
||||
@@ -39,6 +38,7 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..))
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import qualified Simplex.Messaging.Encoding.Base64 as B64
|
||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (SubscriptionMode (..))
|
||||
import Simplex.Messaging.Util (allFinally)
|
||||
|
||||
@@ -14,7 +14,6 @@ import Control.Concurrent.STM
|
||||
import Control.Monad (unless, when)
|
||||
import Control.Monad.Except (runExceptT)
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Char (isDigit)
|
||||
import Data.List (isPrefixOf, isSuffixOf)
|
||||
@@ -35,6 +34,7 @@ import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow, withTransaction)
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff)
|
||||
import qualified Simplex.Messaging.Encoding.Base64 as B64
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Version
|
||||
import System.Directory (doesFileExist)
|
||||
|
||||
@@ -4,12 +4,12 @@ module WebRTCTests where
|
||||
|
||||
import Control.Monad.Except
|
||||
import Crypto.Random (getRandomBytes)
|
||||
import qualified Data.ByteString.Base64.URL as U
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Foreign.StablePtr
|
||||
import Simplex.Chat.Mobile
|
||||
import Simplex.Chat.Mobile.WebRTC
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Encoding.Base64.URL as U
|
||||
import System.FilePath ((</>))
|
||||
import Test.Hspec
|
||||
|
||||
@@ -36,8 +36,8 @@ webRTCTests = describe "WebRTC crypto" $ do
|
||||
cc <- newStablePtr c
|
||||
let key = B.replicate 32 '#'
|
||||
frame <- (<> B.replicate reservedSize '\NUL') <$> getRandomBytes 100
|
||||
runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0"
|
||||
runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0"
|
||||
runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "invalid key: invalid base64 encoding near offset: 0"
|
||||
runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid base64 encoding near offset: 0"
|
||||
it "should fail on invalid auth tag" $ \tmp -> do
|
||||
Right c <- chatMigrateInit (tmp </> "1") "" "yesUp"
|
||||
cc <- newStablePtr c
|
||||
|
||||
Reference in New Issue
Block a user