mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58ad6098aa | |||
| 20b7d6623b | |||
| fe4176357e |
@@ -21,8 +21,6 @@ public let CURRENT_CHAT_VERSION: Int = 2
|
||||
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
|
||||
public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION)
|
||||
|
||||
private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock")
|
||||
|
||||
enum TerminalItem: Identifiable {
|
||||
case cmd(Date, ChatCommand)
|
||||
case resp(Date, ChatResponse)
|
||||
@@ -1568,30 +1566,34 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.removeChat(mergedContact.id)
|
||||
}
|
||||
}
|
||||
case let .networkStatus(status, connections):
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = m.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
for cId in connections {
|
||||
ns[cId] = status
|
||||
case let .contactsSubscribed(_, contactRefs):
|
||||
await updateContactsStatus(contactRefs, status: .connected)
|
||||
case let .contactsDisconnected(_, contactRefs):
|
||||
await updateContactsStatus(contactRefs, status: .disconnected)
|
||||
case let .contactSubSummary(_, contactSubscriptions):
|
||||
await MainActor.run {
|
||||
for sub in contactSubscriptions {
|
||||
// no need to update contact here, and it is slow
|
||||
// if active(user) {
|
||||
// m.updateContact(sub.contact)
|
||||
// }
|
||||
if let err = sub.contactError {
|
||||
processContactSubError(sub.contact, err)
|
||||
} else {
|
||||
m.setContactNetworkStatus(sub.contact, .connected)
|
||||
}
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
m.networkStatuses = ns
|
||||
}
|
||||
case let .networkStatus(status, connections):
|
||||
await MainActor.run {
|
||||
for cId in connections {
|
||||
m.networkStatuses[cId] = status
|
||||
}
|
||||
}
|
||||
case let .networkStatuses(_, statuses): ()
|
||||
// dispatch queue to synchronize access
|
||||
networkStatusesLock.sync {
|
||||
var ns = m.networkStatuses
|
||||
// slow loop is on the background thread
|
||||
await MainActor.run {
|
||||
for s in statuses {
|
||||
ns[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
// fast model update is on the main thread
|
||||
DispatchQueue.main.sync {
|
||||
m.networkStatuses = ns
|
||||
m.networkStatuses[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
}
|
||||
case let .newChatItem(user, aChatItem):
|
||||
@@ -1942,6 +1944,26 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
|
||||
}
|
||||
}
|
||||
|
||||
func updateContactsStatus(_ contactRefs: [ContactRef], status: NetworkStatus) async {
|
||||
let m = ChatModel.shared
|
||||
await MainActor.run {
|
||||
for c in contactRefs {
|
||||
m.networkStatuses[c.agentConnId] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
|
||||
let m = ChatModel.shared
|
||||
var err: String
|
||||
switch chatError {
|
||||
case .errorAgent(agentError: .BROKER(_, .NETWORK)): err = "network"
|
||||
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
||||
default: err = String(describing: chatError)
|
||||
}
|
||||
m.setContactNetworkStatus(contact, .error(connectionError: err))
|
||||
}
|
||||
|
||||
func refreshCallInvitations() throws {
|
||||
let m = ChatModel.shared
|
||||
let callInvitations = try justRefreshCallInvitations()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ struct ChatView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@State @ObservedObject var chat: Chat
|
||||
@State private var showChatInfoSheet: Bool = false
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@@ -235,9 +234,7 @@ struct ChatView: View {
|
||||
|
||||
private func initChatView() {
|
||||
let cInfo = chat.chatInfo
|
||||
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
|
||||
if case .active = scenePhase,
|
||||
case let .direct(contact) = cInfo {
|
||||
if case let .direct(contact) = cInfo {
|
||||
Task {
|
||||
do {
|
||||
let (stats, _) = try await apiContactInfo(chat.chatInfo.apiId)
|
||||
@@ -514,7 +511,6 @@ struct ChatView: View {
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
itemWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
chatView: self
|
||||
@@ -527,7 +523,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 +651,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)
|
||||
|
||||
@@ -12,7 +12,6 @@ private let fillColorLight = Color(uiColor: UIColor(red: 0.99, green: 0.99, blue
|
||||
struct UserPicker: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@Binding var showSettings: Bool
|
||||
@Binding var showConnectDesktop: Bool
|
||||
@Binding var userPickerVisible: Bool
|
||||
@@ -92,10 +91,7 @@ struct UserPicker: View {
|
||||
.opacity(userPickerVisible ? 1.0 : 0.0)
|
||||
.onAppear {
|
||||
do {
|
||||
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
|
||||
if case .active = scenePhase {
|
||||
m.users = try listUsers()
|
||||
}
|
||||
m.users = try listUsers()
|
||||
} catch let error {
|
||||
logger.error("Error loading users \(responseError(error))")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; };
|
||||
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
|
||||
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; };
|
||||
5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */; };
|
||||
5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E702BACC5D600100AD3 /* libgmpxx.a */; };
|
||||
5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E712BACC5D600100AD3 /* libffi.a */; };
|
||||
5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E722BACC5D600100AD3 /* libgmp.a */; };
|
||||
5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */; };
|
||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
|
||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
|
||||
5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */; };
|
||||
@@ -139,11 +144,6 @@
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
|
||||
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
|
||||
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
|
||||
5CF898622BB984E400EE33B6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985D2BB984E400EE33B6 /* libgmpxx.a */; };
|
||||
5CF898632BB984E400EE33B6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985E2BB984E400EE33B6 /* libffi.a */; };
|
||||
5CF898642BB984E400EE33B6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF8985F2BB984E400EE33B6 /* libgmp.a */; };
|
||||
5CF898652BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */; };
|
||||
5CF898662BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */; };
|
||||
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
|
||||
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
|
||||
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
|
||||
@@ -163,11 +163,6 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
@@ -295,6 +290,11 @@
|
||||
5C371E4E2BA9AAA200100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C371E4F2BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = "hu.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C371E502BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C371E702BACC5D600100AD3 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C371E712BACC5D600100AD3 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C371E722BACC5D600100AD3 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a"; sourceTree = "<group>"; };
|
||||
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
|
||||
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = "<group>"; };
|
||||
5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectDesktopView.swift; sourceTree = "<group>"; };
|
||||
@@ -436,11 +436,6 @@
|
||||
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = "<group>"; };
|
||||
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = "<group>"; };
|
||||
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = "<group>"; };
|
||||
5CF8985D2BB984E400EE33B6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CF8985E2BB984E400EE33B6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CF8985F2BB984E400EE33B6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a"; sourceTree = "<group>"; };
|
||||
5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
|
||||
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
|
||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||
@@ -459,11 +454,6 @@
|
||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
@@ -531,13 +521,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CF898652BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a in Frameworks */,
|
||||
5CF898622BB984E400EE33B6 /* libgmpxx.a in Frameworks */,
|
||||
5CF898642BB984E400EE33B6 /* libgmp.a in Frameworks */,
|
||||
5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */,
|
||||
5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */,
|
||||
5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */,
|
||||
5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5CF898632BB984E400EE33B6 /* libffi.a in Frameworks */,
|
||||
5CF898662BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -600,11 +590,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CF8985E2BB984E400EE33B6 /* libffi.a */,
|
||||
5CF8985F2BB984E400EE33B6 /* libgmp.a */,
|
||||
5CF8985D2BB984E400EE33B6 /* libgmpxx.a */,
|
||||
5CF898612BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv-ghc9.6.3.a */,
|
||||
5CF898602BB984E400EE33B6 /* libHSsimplex-chat-5.6.0.4-FOF2McwHkk1EIlP5UNozOv.a */,
|
||||
5C371E712BACC5D600100AD3 /* libffi.a */,
|
||||
5C371E722BACC5D600100AD3 /* libgmp.a */,
|
||||
5C371E702BACC5D600100AD3 /* libgmpxx.a */,
|
||||
5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */,
|
||||
5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1451,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;
|
||||
@@ -1513,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;
|
||||
@@ -1542,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 = 204;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
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.";
|
||||
@@ -1570,13 +1554,11 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
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;
|
||||
};
|
||||
@@ -1591,16 +1573,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 = 204;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
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.";
|
||||
@@ -1619,7 +1597,6 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
@@ -1676,15 +1653,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 = 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";
|
||||
@@ -1695,7 +1669,6 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1703,7 +1676,6 @@
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -1713,15 +1685,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 = 204;
|
||||
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";
|
||||
@@ -1732,7 +1701,6 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1740,7 +1708,6 @@
|
||||
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;
|
||||
@@ -1752,8 +1719,6 @@
|
||||
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 = 204;
|
||||
DEFINES_MODULE = YES;
|
||||
@@ -1762,7 +1727,6 @@
|
||||
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";
|
||||
@@ -1780,7 +1744,6 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
@@ -1790,7 +1753,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";
|
||||
@@ -1803,8 +1765,6 @@
|
||||
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 = 204;
|
||||
DEFINES_MODULE = YES;
|
||||
@@ -1813,7 +1773,6 @@
|
||||
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";
|
||||
@@ -1831,7 +1790,6 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
@@ -1841,7 +1799,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;
|
||||
|
||||
@@ -557,6 +557,11 @@ public enum ChatResponse: Decodable, Error {
|
||||
case contactRequestRejected(user: UserRef)
|
||||
case contactUpdated(user: UserRef, toContact: Contact)
|
||||
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
|
||||
// TODO remove events below
|
||||
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
||||
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
||||
case contactSubSummary(user: UserRef, contactSubscriptions: [ContactSubStatus])
|
||||
// TODO remove events above
|
||||
case networkStatus(networkStatus: NetworkStatus, connections: [String])
|
||||
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||
case groupSubscribed(user: UserRef, groupInfo: GroupRef)
|
||||
@@ -719,6 +724,9 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactRequestRejected: return "contactRequestRejected"
|
||||
case .contactUpdated: return "contactUpdated"
|
||||
case .groupMemberUpdated: return "groupMemberUpdated"
|
||||
case .contactsSubscribed: return "contactsSubscribed"
|
||||
case .contactsDisconnected: return "contactsDisconnected"
|
||||
case .contactSubSummary: return "contactSubSummary"
|
||||
case .networkStatus: return "networkStatus"
|
||||
case .networkStatuses: return "networkStatuses"
|
||||
case .groupSubscribed: return "groupSubscribed"
|
||||
@@ -877,6 +885,9 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
|
||||
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
||||
case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))"
|
||||
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||
case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
|
||||
@@ -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))"
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@ void haskell_init(void) {
|
||||
}
|
||||
|
||||
void haskell_init_nse(void) {
|
||||
int argc = 7;
|
||||
int argc = 9;
|
||||
char *argv[] = {
|
||||
"simplex",
|
||||
"+RTS", // requires `hs_init_with_rtsopts`
|
||||
"-A1m", // chunk size for new allocations
|
||||
"-H1m", // initial heap size
|
||||
"-M12m", // maximum heap size (25 total - 1 grace - 12 for swift), make GC work harder when approaching the limit
|
||||
"-Mgrace=1m", // (default, just to make explicit) extra memory to handle "memory exhausted" exception and fail cleanly
|
||||
"-F0.5", // heap growth triggering GC
|
||||
"-Fd1", // memory return
|
||||
"-c", // compacting garbage collector
|
||||
|
||||
@@ -143,9 +143,6 @@ dependencies {
|
||||
|
||||
implementation("com.jakewharton:process-phoenix:2.2.0")
|
||||
|
||||
//Camera Permission
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.23.0")
|
||||
|
||||
//implementation("androidx.compose.material:material-icons-extended:$compose_version")
|
||||
//implementation("androidx.compose.ui:ui-util:$compose_version")
|
||||
|
||||
|
||||
+1
-33
@@ -1,9 +1,7 @@
|
||||
package chat.simplex.app.views.call
|
||||
|
||||
import android.Manifest
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.os.*
|
||||
@@ -30,7 +28,6 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import chat.simplex.app.*
|
||||
import chat.simplex.app.R
|
||||
@@ -39,12 +36,10 @@ import chat.simplex.app.model.NtfManager
|
||||
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
@@ -114,20 +109,9 @@ class CallActivity: ComponentActivity(), ServiceConnection {
|
||||
m.callCommand.add(WCallCommand.Layout(layoutType))
|
||||
}
|
||||
|
||||
private fun hasGrantedPermissions(): Boolean {
|
||||
val grantedAudio = ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
||||
val grantedCamera = !callSupportsVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
return grantedAudio && grantedCamera
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (isOnLockScreenNow()) {
|
||||
super.onBackPressed()
|
||||
} else if (!hasGrantedPermissions() && !callSupportsVideo()) {
|
||||
val call = m.activeCall.value
|
||||
if (call != null) {
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
}
|
||||
} else {
|
||||
m.activeCallViewIsCollapsed.value = true
|
||||
}
|
||||
@@ -239,21 +223,8 @@ fun CallActivityView() {
|
||||
}
|
||||
Box(Modifier.background(Color.Black)) {
|
||||
if (call != null) {
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = if (callSupportsVideo()) {
|
||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
} else {
|
||||
listOf(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
)
|
||||
if (permissionsState.allPermissionsGranted) {
|
||||
ActiveCallView()
|
||||
} else {
|
||||
CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) {
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
}
|
||||
}
|
||||
val view = LocalView.current
|
||||
ActiveCallView()
|
||||
if (callSupportsVideo()) {
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -281,9 +252,6 @@ fun CallActivityView() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!m.activeCallViewIsCollapsed.value) {
|
||||
AlertManager.shared.showInView()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(call == null) {
|
||||
if (call != null) {
|
||||
|
||||
@@ -90,9 +90,6 @@ kotlin {
|
||||
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
|
||||
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
|
||||
implementation("androidx.camera:camera-view:${cameraXVersion}")
|
||||
|
||||
// Calls lifecycle listener
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.4.1")
|
||||
}
|
||||
}
|
||||
val desktopMain by getting {
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package chat.simplex.common.helpers
|
||||
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.AlertManager
|
||||
import chat.simplex.common.views.helpers.generalGetString
|
||||
import chat.simplex.res.MR
|
||||
|
||||
fun Context.openAppSettingsInSystem() {
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.parse("package:${androidAppContext.packageName}")
|
||||
try {
|
||||
startActivity(this)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.showAllowPermissionInSettingsAlert(action: () -> Unit = ::openAppSettingsInSystem) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.permissions_grant_in_settings),
|
||||
text = generalGetString(MR.strings.permissions_find_in_settings_and_grant),
|
||||
confirmText = generalGetString(MR.strings.permissions_open_settings),
|
||||
onConfirm = action,
|
||||
)
|
||||
}
|
||||
+60
-163
@@ -1,7 +1,5 @@
|
||||
package chat.simplex.common.views.call
|
||||
|
||||
import SectionSpacer
|
||||
import SectionView
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
@@ -26,18 +24,17 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import androidx.webkit.WebViewClientCompat
|
||||
import chat.simplex.common.helpers.showAllowPermissionInSettingsAlert
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.model.ChatModel
|
||||
@@ -45,12 +42,13 @@ import chat.simplex.common.model.Contact
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import com.google.accompanist.permissions.*
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
// Should be destroy()'ed and set as null when call is ended. Otherwise, it will be a leak
|
||||
@@ -211,6 +209,7 @@ actual fun ActiveCallView() {
|
||||
ActiveCallOverlay(call, chatModel, audioViaBluetooth)
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
DisposableEffect(Unit) {
|
||||
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
|
||||
@@ -511,138 +510,34 @@ private fun DisabledBackgroundCallsButton() {
|
||||
// }
|
||||
//}
|
||||
|
||||
@Composable
|
||||
fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Unit) {
|
||||
val audioPermission = rememberPermissionState(Manifest.permission.RECORD_AUDIO)
|
||||
val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA)
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = if (hasVideo) {
|
||||
listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
} else {
|
||||
listOf(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
)
|
||||
val context = LocalContext.current
|
||||
val buttonEnabled = remember { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
if (!pipActive) {
|
||||
permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert)
|
||||
}
|
||||
}
|
||||
|
||||
if (pipActive) {
|
||||
Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceEvenly) {
|
||||
if (audioPermission.status is PermissionStatus.Denied) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_call_500),
|
||||
stringResource(MR.strings.permissions_record_audio),
|
||||
Modifier.size(24.dp),
|
||||
tint = Color(0xFFFFFFD8)
|
||||
)
|
||||
}
|
||||
if (hasVideo && cameraPermission.status is PermissionStatus.Denied) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_videocam),
|
||||
stringResource(MR.strings.permissions_camera),
|
||||
Modifier.size(24.dp),
|
||||
tint = Color(0xFFFFFFD8)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ColumnWithScrollBar(Modifier.fillMaxSize()) {
|
||||
Spacer(Modifier.height(AppBarHeight))
|
||||
|
||||
AppBarTitle(stringResource(MR.strings.permissions_required))
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
val onClick = {
|
||||
if (permissionsState.shouldShowRationale) {
|
||||
context.showAllowPermissionInSettingsAlert()
|
||||
} else {
|
||||
permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert)
|
||||
}
|
||||
}
|
||||
Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8))
|
||||
SectionSpacer()
|
||||
SectionView {
|
||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_camera_and_record_audio)
|
||||
} else if (audioPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_record_audio)
|
||||
} else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) {
|
||||
stringResource(MR.strings.permissions_camera)
|
||||
} else ""
|
||||
GrantPermissionButton(text, buttonEnabled.value, onClick)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = if (hasVideo) 0.dp else DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.Center) {
|
||||
SimpleButtonFrame(cancel, Modifier.height(64.dp)) {
|
||||
Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GrantPermissionButton(text: String, enabled: Boolean, onClick: () -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.heightIn(min = 30.dp)
|
||||
.background(WarningOrange.copy(0.3f), RoundedCornerShape(50)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), fontSize = 20.sp, color = WarningOrange)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The idea of this function is to ask system to show permission dialog and to see if it's really doing it.
|
||||
* Otherwise, show alert with a button that opens settings for manual permission granting
|
||||
* */
|
||||
private fun MultiplePermissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled: MutableState<Boolean>, fallback: () -> Unit) {
|
||||
buttonEnabled.value = false
|
||||
val lifecycleOwner = ProcessLifecycleOwner.get().lifecycle
|
||||
var useFallback = true
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||
useFallback = false
|
||||
buttonEnabled.value = true
|
||||
}
|
||||
}
|
||||
lifecycleOwner.addObserver(observer)
|
||||
withBGApi {
|
||||
delay(2000)
|
||||
if (useFallback && chatModel.activeCall.value != null) {
|
||||
fallback()
|
||||
}
|
||||
buttonEnabled.value = true
|
||||
}.invokeOnCompletion {
|
||||
// Main thread only
|
||||
withApi {
|
||||
lifecycleOwner.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
launchMultiplePermissionRequest()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
||||
val webView = remember { mutableStateOf<WebView?>(null) }
|
||||
val permissionsState = rememberMultiplePermissionsState(
|
||||
permissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.MODIFY_AUDIO_SETTINGS,
|
||||
Manifest.permission.INTERNET
|
||||
)
|
||||
)
|
||||
fun processCommand(wv: WebView, cmd: WCallCommand) {
|
||||
val apiCall = WVAPICall(command = cmd)
|
||||
wv.evaluateJavascript("processCommand(${json.encodeToString(apiCall)})", null)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME || event == Lifecycle.Event.ON_START) {
|
||||
permissionsState.launchMultiplePermissionRequest()
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
// val wv = webView.value
|
||||
// if (wv != null) processCommand(wv, WCallCommand.End)
|
||||
// webView.value?.destroy()
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
// val wv = webView.value
|
||||
// if (wv != null) processCommand(wv, WCallCommand.End)
|
||||
// webView.value?.destroy()
|
||||
webView.value = null
|
||||
}
|
||||
}
|
||||
@@ -665,42 +560,44 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
|
||||
.addPathHandler("/assets/www/", WebViewAssetLoader.AssetsPathHandler(LocalContext.current))
|
||||
.build()
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AndroidView(
|
||||
factory = { AndroidViewContext ->
|
||||
(staticWebView ?: WebView(androidAppContext)).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
this.webChromeClient = object: WebChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
if (request.origin.toString().startsWith("file:/")) {
|
||||
request.grant(request.resources)
|
||||
} else {
|
||||
Log.d(TAG, "Permission request from webview denied.")
|
||||
request.deny()
|
||||
if (permissionsState.allPermissionsGranted) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AndroidView(
|
||||
factory = { AndroidViewContext ->
|
||||
(staticWebView ?: WebView(androidAppContext)).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
this.webChromeClient = object: WebChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
if (request.origin.toString().startsWith("file:/")) {
|
||||
request.grant(request.resources)
|
||||
} else {
|
||||
Log.d(TAG, "Permission request from webview denied.")
|
||||
request.deny()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.webViewClient = LocalContentWebViewClient(webView, assetLoader)
|
||||
this.clearHistory()
|
||||
this.clearCache(true)
|
||||
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
|
||||
val webViewSettings = this.settings
|
||||
webViewSettings.allowFileAccess = true
|
||||
webViewSettings.allowContentAccess = true
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
||||
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
if (staticWebView == null) {
|
||||
this.loadUrl("file:android_asset/www/android/call.html")
|
||||
} else {
|
||||
webView.value = this
|
||||
this.webViewClient = LocalContentWebViewClient(webView, assetLoader)
|
||||
this.clearHistory()
|
||||
this.clearCache(true)
|
||||
this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface")
|
||||
val webViewSettings = this.settings
|
||||
webViewSettings.allowFileAccess = true
|
||||
webViewSettings.allowContentAccess = true
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
||||
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
if (staticWebView == null) {
|
||||
this.loadUrl("file:android_asset/www/android/call.html")
|
||||
} else {
|
||||
webView.value = this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) { /* WebView */ }
|
||||
) { /* WebView */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ if(NOT APPLE)
|
||||
else()
|
||||
# Without direct linking it can't find hs_init in linking step
|
||||
add_library( rts SHARED IMPORTED )
|
||||
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/deps/libHSrts*_thr-*.${OS_LIB_EXT})
|
||||
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/libHSrts*_thr-*.${OS_LIB_EXT})
|
||||
set_target_properties( rts PROPERTIES IMPORTED_LOCATION ${RTSLIB})
|
||||
|
||||
target_link_libraries(app-lib rts simplex)
|
||||
|
||||
-2
@@ -1753,8 +1753,6 @@ object ChatController {
|
||||
chatModel.removeChat(rhId, r.mergedContact.id)
|
||||
}
|
||||
}
|
||||
// ContactsSubscribed, ContactsDisconnected and ContactSubSummary are only used in CLI,
|
||||
// They have to be used here for remote desktop to process these status updates.
|
||||
is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected())
|
||||
is CR.ContactsDisconnected -> updateContactsStatus(r.contactRefs, NetworkStatus.Disconnected())
|
||||
is CR.ContactSubSummary -> {
|
||||
|
||||
+1
-4
@@ -190,7 +190,6 @@ class AlertManager {
|
||||
fun showAlertMsg(
|
||||
title: String, text: String? = null,
|
||||
confirmText: String = generalGetString(MR.strings.ok),
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
shareText: Boolean? = null
|
||||
) {
|
||||
@@ -221,7 +220,6 @@ class AlertManager {
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm?.invoke()
|
||||
hideAlert()
|
||||
},
|
||||
Modifier.focusRequester(focusRequester)
|
||||
@@ -259,9 +257,8 @@ class AlertManager {
|
||||
title: StringResource,
|
||||
text: StringResource? = null,
|
||||
confirmText: StringResource = MR.strings.ok,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm, hostDevice)
|
||||
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), hostDevice)
|
||||
|
||||
@Composable
|
||||
fun showInView() {
|
||||
|
||||
@@ -814,14 +814,6 @@
|
||||
<!-- CallView -->
|
||||
<string name="unable_to_open_browser_title">Error opening browser</string>
|
||||
<string name="unable_to_open_browser_desc">The default web browser is required for calls. Please configure the default browser in the system, and share more information with the developers.</string>
|
||||
<string name="permissions_required">Grant permissions</string>
|
||||
<string name="permissions_record_audio">Microphone</string>
|
||||
<string name="permissions_camera">Camera</string>
|
||||
<string name="permissions_camera_and_record_audio">Camera and microphone</string>
|
||||
<string name="permissions_grant">Grant permission(s) to make calls</string>
|
||||
<string name="permissions_grant_in_settings">Grant in settings</string>
|
||||
<string name="permissions_find_in_settings_and_grant">Find this permission in Android settings and grant it manually.</string>
|
||||
<string name="permissions_open_settings">Open settings</string>
|
||||
|
||||
<!-- SimpleXInfo -->
|
||||
<string name="next_generation_of_private_messaging">The next generation of private messaging</string>
|
||||
|
||||
@@ -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") }*/
|
||||
}
|
||||
|
||||
@@ -1,8 +1,61 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
||||
module Main where
|
||||
|
||||
import Server (simplexChatServer)
|
||||
import Simplex.Chat.Terminal (terminalChatConfig)
|
||||
import Simplex.Chat.Terminal.Main (simplexChatCLI)
|
||||
import Control.Concurrent (forkIO, threadDelay)
|
||||
import Control.Concurrent.STM
|
||||
import Control.Monad
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Data.Time.LocalTime (getCurrentTimeZone)
|
||||
import Server
|
||||
import Simplex.Chat.Controller (ChatController (..), ChatResponse (..), currentRemoteHost, versionNumber, versionString)
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Terminal
|
||||
import Simplex.Chat.View (serializeChatResponse)
|
||||
import Simplex.Messaging.Client (NetworkConfig (..))
|
||||
import System.Directory (getAppUserDataDirectory)
|
||||
import System.Terminal (withTerminal)
|
||||
|
||||
main :: IO ()
|
||||
main = simplexChatCLI terminalChatConfig (Just simplexChatServer)
|
||||
main = do
|
||||
appDir <- getAppUserDataDirectory "simplex"
|
||||
opts@ChatOpts {chatCmd, chatServerPort} <- getChatOpts appDir "simplex_v1"
|
||||
if null chatCmd
|
||||
then case chatServerPort of
|
||||
Just chatPort -> simplexChatServer defaultChatServerConfig {chatPort} terminalChatConfig opts
|
||||
_ -> runCLI opts
|
||||
else simplexChatCore terminalChatConfig opts $ runCommand opts
|
||||
where
|
||||
runCLI opts = do
|
||||
welcome opts
|
||||
t <- withTerminal pure
|
||||
simplexChatTerminal terminalChatConfig opts t
|
||||
runCommand ChatOpts {chatCmd, chatCmdLog, chatCmdDelay} user cc = do
|
||||
when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do
|
||||
(_, _, r') <- atomically . readTBQueue $ outputQ cc
|
||||
case r' of
|
||||
CRNewChatItem {} -> printResponse r'
|
||||
_ -> when (chatCmdLog == CCLAll) $ printResponse r'
|
||||
sendChatCmdStr cc chatCmd >>= printResponse
|
||||
threadDelay $ chatCmdDelay * 1000000
|
||||
where
|
||||
printResponse r = do
|
||||
ts <- getCurrentTime
|
||||
tz <- getCurrentTimeZone
|
||||
rh <- readTVarIO $ currentRemoteHost cc
|
||||
putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r
|
||||
|
||||
welcome :: ChatOpts -> IO ()
|
||||
welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} =
|
||||
mapM_
|
||||
putStrLn
|
||||
[ versionString versionNumber,
|
||||
"db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db",
|
||||
maybe
|
||||
"direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050"
|
||||
(("using SOCKS5 proxy " <>) . show)
|
||||
(socksProxy networkConfig),
|
||||
"type \"/help\" or \"/h\" for usage info"
|
||||
]
|
||||
|
||||
@@ -28,9 +28,9 @@ import Simplex.Messaging.Util (raceAny_)
|
||||
import UnliftIO.Exception
|
||||
import UnliftIO.STM
|
||||
|
||||
simplexChatServer :: ServiceName -> ChatConfig -> ChatOpts -> IO ()
|
||||
simplexChatServer chatPort cfg opts =
|
||||
simplexChatCore cfg opts . const $ runChatServer defaultChatServerConfig {chatPort}
|
||||
simplexChatServer :: ChatServerConfig -> ChatConfig -> ChatOpts -> IO ()
|
||||
simplexChatServer srvCfg cfg opts =
|
||||
simplexChatCore cfg opts . const $ runChatServer srvCfg
|
||||
|
||||
data ChatServerConfig = ChatServerConfig
|
||||
{ chatPort :: ServiceName,
|
||||
|
||||
@@ -70,7 +70,7 @@ crDirectoryEvent = \case
|
||||
CRChatItemDeleted {deletedChatItem = AChatItem _ SMDRcv (DirectChat ct) _, byUser = False} -> Just $ DEItemDeleteIgnored ct
|
||||
CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} ->
|
||||
Just $ case (mc, itemLive) of
|
||||
(MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t
|
||||
(MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly directoryCmdP $ T.dropWhileEnd isSpace t
|
||||
_ -> DEUnsupportedMessage ct ciId
|
||||
where
|
||||
ciId = chatItemId' ci
|
||||
|
||||
+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: 1e6268cc1dbba69639425f7d5a6c9a07995e0bb2
|
||||
tag: ee90ea6a69fe8283d37d9821cd83798fd0a76260
|
||||
|
||||
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.
|
||||
Generated
+227
-468
@@ -16,21 +16,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"blank": {
|
||||
"locked": {
|
||||
"lastModified": 1625557891,
|
||||
"narHash": "sha256-O8/MWsPBGhhyPoPLHZAuoZiiHo9q6FLlEeIDEXuj6T4=",
|
||||
"owner": "divnix",
|
||||
"repo": "blank",
|
||||
"rev": "5a5d2684073d9f563072ed07c871d577a6c614a8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "blank",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cabal-32": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -98,64 +83,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1663445644,
|
||||
"narHash": "sha256-+xVlcK60x7VY1vRJbNUEAHi17ZuoQxAIH4S4iUFUGBA=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "e3dc3e21594fe07bdb24bdf1c8657acaa4cb8f66",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"dmerge": {
|
||||
"inputs": {
|
||||
"nixlib": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
],
|
||||
"yants": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"yants"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659548052,
|
||||
"narHash": "sha256-fzI2gp1skGA8mQo/FBFrUAtY0GQkAIAaV/V127TJPyY=",
|
||||
"owner": "divnix",
|
||||
"repo": "data-merge",
|
||||
"rev": "d160d18ce7b1a45b88344aa3f13ed1163954b497",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "data-merge",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -173,74 +100,34 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1650374568,
|
||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||
"lastModified": 1698579227,
|
||||
"narHash": "sha256-KVWjFZky+gRuWennKsbo6cWyo7c/z/VgCte5pR9pEKg=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f76e870d64779109e41370848074ac4eaa1606ec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1676283394,
|
||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||
"type": "github"
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"locked": {
|
||||
"lastModified": 1653893745,
|
||||
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_4": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -266,33 +153,51 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"utils": "utils"
|
||||
},
|
||||
"ghc98X": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1655245309,
|
||||
"narHash": "sha256-d/YPoQ/vFn1+GTmSdvbSBSTOai61FONxB4+Lt6w/IVI=",
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
|
||||
"type": "github"
|
||||
"lastModified": 1696643148,
|
||||
"narHash": "sha256-E02DfgISH7EvvNAu0BHiPvl1E5FGMDi0pWdNZtIBC9I=",
|
||||
"ref": "ghc-9.8",
|
||||
"rev": "443e870d977b1ab6fc05f47a9a17bc49296adbd6",
|
||||
"revCount": 61642,
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
"ref": "ghc-9.8",
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
||||
}
|
||||
},
|
||||
"ghc99": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1697054644,
|
||||
"narHash": "sha256-kKarOuXUaAH3QWv7ASx+gGFMHaHKe0pK5Zu37ky2AL4=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "f383a242c76f90bcca8a4d7ee001dcb49c172a9a",
|
||||
"revCount": 62040,
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
||||
},
|
||||
"original": {
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
||||
}
|
||||
},
|
||||
"hackage": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1702340598,
|
||||
"narHash": "sha256-CC0HI+6iKPtH+8r/ZfcpW5v/OYvL7zMwpr0xfkXV1zU=",
|
||||
"lastModified": 1702513363,
|
||||
"narHash": "sha256-kloro9uEe8aYhPMoMjVNq2rfrXNgMOZhOPwVH5DH2K0=",
|
||||
"owner": "input-output-hk",
|
||||
"repo": "hackage.nix",
|
||||
"rev": "24617c569995e38bf3b83b48eec6628a50fdb4fb",
|
||||
"rev": "a9d931d0398da67846fa257922a924829233cb91",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -309,33 +214,40 @@
|
||||
"cabal-36": "cabal-36",
|
||||
"cardano-shell": "cardano-shell",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"ghc-8.6.5-iohk": "ghc-8.6.5-iohk",
|
||||
"ghc98X": "ghc98X",
|
||||
"ghc99": "ghc99",
|
||||
"hackage": [
|
||||
"hackage"
|
||||
],
|
||||
"hls-1.10": "hls-1.10",
|
||||
"hls-2.0": "hls-2.0",
|
||||
"hls-2.2": "hls-2.2",
|
||||
"hls-2.3": "hls-2.3",
|
||||
"hls-2.4": "hls-2.4",
|
||||
"hpc-coveralls": "hpc-coveralls",
|
||||
"hydra": "hydra",
|
||||
"iserv-proxy": "iserv-proxy",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
"haskellNix",
|
||||
"nixpkgs-unstable"
|
||||
],
|
||||
"nixpkgs-2003": "nixpkgs-2003",
|
||||
"nixpkgs-2105": "nixpkgs-2105",
|
||||
"nixpkgs-2111": "nixpkgs-2111",
|
||||
"nixpkgs-2205": "nixpkgs-2205",
|
||||
"nixpkgs-2211": "nixpkgs-2211",
|
||||
"nixpkgs-2305": "nixpkgs-2305",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"old-ghc-nix": "old-ghc-nix",
|
||||
"stackage": "stackage",
|
||||
"tullia": "tullia"
|
||||
"stackage": "stackage"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1677975916,
|
||||
"narHash": "sha256-dbe8lEEPyfzjdRwpePClv7J9p9lQg7BwbBqAMCw4RLw=",
|
||||
"lastModified": 1701163700,
|
||||
"narHash": "sha256-sOrewUS3LnzV09nGr7+3R6Q6zsgU4smJc61QsHq+4DE=",
|
||||
"owner": "input-output-hk",
|
||||
"repo": "haskell.nix",
|
||||
"rev": "ab5efd87ce3fd8ade38a01d97693d29a4f1ae7e4",
|
||||
"rev": "2808bfe3e62e9eb4ee8974cd623a00e1611f302b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -345,6 +257,91 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hls-1.10": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1680000865,
|
||||
"narHash": "sha256-rc7iiUAcrHxwRM/s0ErEsSPxOR3u8t7DvFeWlMycWgo=",
|
||||
"owner": "haskell",
|
||||
"repo": "haskell-language-server",
|
||||
"rev": "b08691db779f7a35ff322b71e72a12f6e3376fd9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "haskell",
|
||||
"ref": "1.10.0.0",
|
||||
"repo": "haskell-language-server",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hls-2.0": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1687698105,
|
||||
"narHash": "sha256-OHXlgRzs/kuJH8q7Sxh507H+0Rb8b7VOiPAjcY9sM1k=",
|
||||
"owner": "haskell",
|
||||
"repo": "haskell-language-server",
|
||||
"rev": "783905f211ac63edf982dd1889c671653327e441",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "haskell",
|
||||
"ref": "2.0.0.1",
|
||||
"repo": "haskell-language-server",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hls-2.2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1693064058,
|
||||
"narHash": "sha256-8DGIyz5GjuCFmohY6Fa79hHA/p1iIqubfJUTGQElbNk=",
|
||||
"owner": "haskell",
|
||||
"repo": "haskell-language-server",
|
||||
"rev": "b30f4b6cf5822f3112c35d14a0cba51f3fe23b85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "haskell",
|
||||
"ref": "2.2.0.0",
|
||||
"repo": "haskell-language-server",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hls-2.3": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1695910642,
|
||||
"narHash": "sha256-tR58doOs3DncFehHwCLczJgntyG/zlsSd7DgDgMPOkI=",
|
||||
"owner": "haskell",
|
||||
"repo": "haskell-language-server",
|
||||
"rev": "458ccdb55c9ea22cd5d13ec3051aaefb295321be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "haskell",
|
||||
"ref": "2.3.0.0",
|
||||
"repo": "haskell-language-server",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hls-2.4": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696939266,
|
||||
"narHash": "sha256-VOMf5+kyOeOmfXTHlv4LNFJuDGa7G3pDnOxtzYR40IU=",
|
||||
"owner": "haskell",
|
||||
"repo": "haskell-language-server",
|
||||
"rev": "362fdd1293efb4b82410b676ab1273479f6d17ee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "haskell",
|
||||
"ref": "2.4.0.0",
|
||||
"repo": "haskell-language-server",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hpc-coveralls": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -384,37 +381,14 @@
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"incl": {
|
||||
"inputs": {
|
||||
"nixlib": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1669263024,
|
||||
"narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=",
|
||||
"owner": "divnix",
|
||||
"repo": "incl",
|
||||
"rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "incl",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"iserv-proxy": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1670983692,
|
||||
"narHash": "sha256-avLo34JnI9HNyOuauK5R69usJm+GfW3MlyGlYxZhTgY=",
|
||||
"lastModified": 1691634696,
|
||||
"narHash": "sha256-MZH2NznKC/gbgBu8NgIibtSUZeJ00HTLJ0PlWKCBHb0=",
|
||||
"ref": "hkm/remote-iserv",
|
||||
"rev": "50d0abb3317ac439a4e7495b185a64af9b7b9300",
|
||||
"revCount": 10,
|
||||
"rev": "43a979272d9addc29fbffc2e8542c5d96e993d73",
|
||||
"revCount": 14,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/hamishmack/iserv-proxy.git"
|
||||
},
|
||||
@@ -440,32 +414,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"n2c": {
|
||||
"mac2ios": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
]
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1665039323,
|
||||
"narHash": "sha256-SAh3ZjFGsaCI8FRzXQyp56qcGdAqgKEfJWPCQ0Sr7tQ=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "b008fe329ffb59b67bf9e7b08ede6ee792f2741a",
|
||||
"lastModified": 1699767871,
|
||||
"narHash": "sha256-kxeCUfwC/Vgh2FvVMlBUq0eVx1JvfHyN+5MPKUik9mE=",
|
||||
"owner": "zw3rk",
|
||||
"repo": "mobile-core-tools",
|
||||
"rev": "4dcb77d5ea896d749381806dfab5358851b08951",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"owner": "zw3rk",
|
||||
"repo": "mobile-core-tools",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -490,95 +454,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-nomad": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"nix2container",
|
||||
"flake-utils"
|
||||
],
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-lib": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1658277770,
|
||||
"narHash": "sha256-T/PgG3wUn8Z2rnzfxf2VqlR1CBjInPE0l1yVzXxPnt0=",
|
||||
"owner": "tristanpemble",
|
||||
"repo": "nix-nomad",
|
||||
"rev": "054adcbdd0a836ae1c20951b67ed549131fd2d70",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tristanpemble",
|
||||
"repo": "nix-nomad",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix2container": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1658567952,
|
||||
"narHash": "sha256-XZ4ETYAMU7XcpEeAFP3NOl9yDXNuZAen/aIJ84G+VgA=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "60bb43d405991c1378baf15a40b5811a53e32ffa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixago": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixago-exts": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1661824785,
|
||||
"narHash": "sha256-/PnwdWoO/JugJZHtDUioQp3uRiWeXHUdgvoyNbXesz8=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixago",
|
||||
"rev": "8c1f9e5f1578d4b2ea989f618588d62a335083c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixago",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1657693803,
|
||||
@@ -645,11 +520,11 @@
|
||||
},
|
||||
"nixpkgs-2205": {
|
||||
"locked": {
|
||||
"lastModified": 1672580127,
|
||||
"narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
|
||||
"lastModified": 1685573264,
|
||||
"narHash": "sha256-Zffu01pONhs/pqH07cjlF10NnMDLok8ix5Uk4rhOnZQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0874168639713f547c05947c76124f78441ea46c",
|
||||
"rev": "380be19fbd2d9079f677978361792cb25e8a3635",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -661,11 +536,11 @@
|
||||
},
|
||||
"nixpkgs-2211": {
|
||||
"locked": {
|
||||
"lastModified": 1675730325,
|
||||
"narHash": "sha256-uNvD7fzO5hNlltNQUAFBPlcEjNG5Gkbhl/ROiX+GZU4=",
|
||||
"lastModified": 1688392541,
|
||||
"narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b7ce17b1ebf600a72178f6302c77b6382d09323f",
|
||||
"rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -675,6 +550,40 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-2305": {
|
||||
"locked": {
|
||||
"lastModified": 1695416179,
|
||||
"narHash": "sha256-610o1+pwbSu+QuF3GE0NU5xQdTHM3t9wyYhB9l94Cd8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "715d72e967ec1dd5ecc71290ee072bcaf5181ed6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-23.05-darwin",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1696019113,
|
||||
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
@@ -693,11 +602,11 @@
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1675758091,
|
||||
"narHash": "sha256-7gFSQbSVAFUHtGCNHPF7mPc5CcqDk9M2+inlVPZSneg=",
|
||||
"lastModified": 1695318763,
|
||||
"narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "747927516efcb5e31ba03b7ff32f61f6d47e7d87",
|
||||
"rev": "e12483116b3b51a185a33a272bf351e357ba9a99",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -709,82 +618,20 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1653581809,
|
||||
"narHash": "sha256-Uvka0V5MTGbeOfWte25+tfRL3moECDh1VwokWSZUdoY=",
|
||||
"lastModified": 1698434055,
|
||||
"narHash": "sha256-Phxi5mUKSoL7A0IYUiYtkI9e8NcGaaV5PJEaJApU1Ko=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "83658b28fe638a170a19b8933aa008b30640fbd1",
|
||||
"rev": "1a3c95e3b23b3cdb26750621c08cc2f1560cb883",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1654807842,
|
||||
"narHash": "sha256-ADymZpr6LuTEBXcy6RtFHcUZdjKTBRTMYwu19WOx17E=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fc909087cc3386955f21b4665731dbdaceefb1d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1665087388,
|
||||
"narHash": "sha256-FZFPuW9NWHJteATOf79rZfwfRn5fE0wi9kRzvGfDHPA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "95fda953f6db2e9496d2682c4fc7b82f959878f7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1676726892,
|
||||
"narHash": "sha256-M7OYVR6dKmzmlebIjybFf3l18S2uur8lMyWWnHQooLY=",
|
||||
"owner": "angerman",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "729469087592bdea58b360de59dadf6d58714c42",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "angerman",
|
||||
"ref": "release-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nosys": {
|
||||
"locked": {
|
||||
"lastModified": 1667881534,
|
||||
"narHash": "sha256-FhwJ15uPLRsvaxtt/bNuqE/ykMpNAPF0upozFKhTtXM=",
|
||||
"owner": "divnix",
|
||||
"repo": "nosys",
|
||||
"rev": "2d0d5207f6a230e9d0f660903f8db9807b54814f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "nosys",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"old-ghc-nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -807,17 +654,21 @@
|
||||
"flake-utils": "flake-utils",
|
||||
"hackage": "hackage",
|
||||
"haskellNix": "haskellNix",
|
||||
"nixpkgs": "nixpkgs_5"
|
||||
"mac2ios": "mac2ios",
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"nixpkgs-2305"
|
||||
]
|
||||
}
|
||||
},
|
||||
"stackage": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1677888571,
|
||||
"narHash": "sha256-YkhRNOaN6QVagZo1cfykYV8KqkI8/q6r2F5+jypOma4=",
|
||||
"lastModified": 1699834215,
|
||||
"narHash": "sha256-g/JKy0BCvJaxPuYDl3QVc4OY8cFEomgG+hW/eEV470M=",
|
||||
"owner": "input-output-hk",
|
||||
"repo": "stackage.nix",
|
||||
"rev": "cb50e6fabdfb2d7e655059039012ad0623f06a27",
|
||||
"rev": "47aacd04abcce6bad57f43cbbbd133538380248e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -826,110 +677,18 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"std": {
|
||||
"inputs": {
|
||||
"arion": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"blank": "blank",
|
||||
"devshell": "devshell",
|
||||
"dmerge": "dmerge",
|
||||
"flake-utils": "flake-utils_4",
|
||||
"incl": "incl",
|
||||
"makes": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"microvm": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"n2c": "n2c",
|
||||
"nixago": "nixago",
|
||||
"nixpkgs": "nixpkgs_4",
|
||||
"nosys": "nosys",
|
||||
"yants": "yants"
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1674526466,
|
||||
"narHash": "sha256-tMTaS0bqLx6VJ+K+ZT6xqsXNpzvSXJTmogkraBGzymg=",
|
||||
"owner": "divnix",
|
||||
"repo": "std",
|
||||
"rev": "516387e3d8d059b50e742a2ff1909ed3c8f82826",
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "std",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"tullia": {
|
||||
"inputs": {
|
||||
"nix-nomad": "nix-nomad",
|
||||
"nix2container": "nix2container",
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"std": "std"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1675695930,
|
||||
"narHash": "sha256-B7rEZ/DBUMlK1AcJ9ajnAPPxqXY6zW2SBX+51bZV0Ac=",
|
||||
"owner": "input-output-hk",
|
||||
"repo": "tullia",
|
||||
"rev": "621365f2c725608f381b3ad5b57afef389fd4c31",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "input-output-hk",
|
||||
"repo": "tullia",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"locked": {
|
||||
"lastModified": 1653893745,
|
||||
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"yants": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"haskellNix",
|
||||
"tullia",
|
||||
"std",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667096281,
|
||||
"narHash": "sha256-wRRec6ze0gJHmGn6m57/zhz/Kdvp9HS4Nl5fkQ+uIuA=",
|
||||
"owner": "divnix",
|
||||
"repo": "yants",
|
||||
"rev": "d18f356ec25cb94dc9c275870c3a7927a10f8c3c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "yants",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
description = "nix flake for simplex-chat";
|
||||
inputs.nixpkgs.url = "github:angerman/nixpkgs/release-22.11";
|
||||
inputs.haskellNix.url = "github:input-output-hk/haskell.nix/armv7a";
|
||||
inputs.haskellNix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs.follows = "haskellNix/nixpkgs-2305";
|
||||
inputs.mac2ios.url = "github:zw3rk/mobile-core-tools";
|
||||
inputs.hackage = {
|
||||
url = "github:input-output-hk/hackage.nix";
|
||||
flake = false;
|
||||
};
|
||||
inputs.haskellNix.inputs.hackage.follows = "hackage";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
outputs = { self, haskellNix, nixpkgs, flake-utils, ... }:
|
||||
outputs = { self, haskellNix, nixpkgs, flake-utils, mac2ios, ... }:
|
||||
let systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; in
|
||||
flake-utils.lib.eachSystem systems (system:
|
||||
# this android26 overlay makes the pkgsCross.{aarch64-android,armv7a-android-prebuilt} to set stdVer to 26 (Android 8).
|
||||
@@ -30,7 +30,7 @@
|
||||
# `appendOverlays` with a singleton is identical to `extend`.
|
||||
let pkgs = haskellNix.legacyPackages.${system}.appendOverlays [android26]; in
|
||||
let drv' = { extra-modules, pkgs', ... }: pkgs'.haskell-nix.project {
|
||||
compiler-nix-name = "ghc8107";
|
||||
compiler-nix-name = "ghc963";
|
||||
index-state = "2023-12-12T00:00:00Z";
|
||||
# We need this, to specify we want the cabal project.
|
||||
# If the stack.yaml was dropped, this would not be necessary.
|
||||
@@ -40,9 +40,12 @@
|
||||
src = ./.;
|
||||
};
|
||||
sha256map = import ./scripts/nix/sha256map.nix;
|
||||
modules = [{
|
||||
modules = [
|
||||
({ pkgs, lib, ...}: lib.mkIf (!pkgs.stdenv.hostPlatform.isWindows) {
|
||||
# This patch adds `dl` as an extra-library to direct-sqlciper, which is needed
|
||||
# on pretty much all unix platforms, but then blows up on windows m(
|
||||
packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-2.3.27.patch ];
|
||||
}
|
||||
})
|
||||
({ pkgs,lib, ... }: lib.mkIf (pkgs.stdenv.hostPlatform.isAndroid) {
|
||||
packages.simplex-chat.components.library.ghcOptions = [ "-pie" ];
|
||||
})] ++ extra-modules;
|
||||
@@ -64,6 +67,9 @@
|
||||
}); in
|
||||
let iosPostInstall = bundleName: ''
|
||||
${pkgs.tree}/bin/tree $out
|
||||
mkdir tmp
|
||||
find ./dist -name "libHS*-ghc*.a" -exec cp {} tmp \;
|
||||
(cd tmp; ${pkgs.tree}/bin/tree .; ar x libHS*.a; for o in *.o; do if /usr/bin/otool -xv $o|grep ldadd ; then echo $o; fi; done; cd ..; rm -fR tmp)
|
||||
mkdir -p $out/_pkg
|
||||
# copy over includes, we might want those, but maybe not.
|
||||
# cp -r $out/lib/*/*/include $out/_pkg/
|
||||
@@ -74,6 +80,18 @@
|
||||
find ${pkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \;
|
||||
# There is no static libc
|
||||
${pkgs.tree}/bin/tree $out/_pkg
|
||||
for pkg in $out/_pkg/*.a; do
|
||||
chmod +w $pkg
|
||||
${mac2ios.packages.${system}.mac2ios}/bin/mac2ios $pkg
|
||||
chmod -w $pkg
|
||||
done
|
||||
|
||||
mkdir tmp
|
||||
find $out/_pkg -name "libHS*-ghc*.a" -exec cp {} tmp \;
|
||||
(cd tmp; ${pkgs.tree}/bin/tree .; ar x libHS*.a; for o in *.o; do if /usr/bin/otool -xv $o|grep ldadd ; then echo $o; fi; done; cd ..; rm -fR tmp)
|
||||
|
||||
sha256sum $out/_pkg/*.a
|
||||
|
||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/${bundleName}.zip *)
|
||||
rm -fR $out/_pkg
|
||||
mkdir -p $out/nix-support
|
||||
@@ -119,13 +137,149 @@
|
||||
hardeningDisable = [ "fortify" ];
|
||||
}
|
||||
);in {
|
||||
# STATIC x86_64-linux
|
||||
"${pkgs.pkgsCross.musl64.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl64).simplex-chat.components.exes.simplex-chat;
|
||||
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl32).simplex-chat.components.exes.simplex-chat;
|
||||
# STATIC i686-linux
|
||||
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv' {
|
||||
pkgs' = pkgs.pkgsCross.musl32;
|
||||
extra-modules = [{
|
||||
# 32 bit patches
|
||||
packages.basement.patches = [
|
||||
./scripts/nix/basement-pr-573.patch
|
||||
];
|
||||
packages.memory.patches = [
|
||||
./scripts/nix/memory-pr-99.patch
|
||||
];
|
||||
}];
|
||||
}).simplex-chat.components.exes.simplex-chat;
|
||||
# WINDOWS x86_64-mingwW64
|
||||
"${pkgs.pkgsCross.mingwW64.hostPlatform.system}:exe:simplex-chat" = (drv' {
|
||||
pkgs' = pkgs.pkgsCross.mingwW64;
|
||||
extra-modules = [{
|
||||
packages.direct-sqlcipher.flags.openssl = true;
|
||||
packages.bitvec.flags.simd = false;
|
||||
packages.direct-sqlcipher.patches = [
|
||||
./scripts/nix/direct-sqlcipher-2.3.27-win.patch
|
||||
];
|
||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||
(pkgs.pkgsCross.mingwW64.openssl) #.override) # { static = true; enableKTLS = false; })
|
||||
];
|
||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||
(pkgs.pkgsCross.mingwW64.openssl) #.override) # { static = true; enableKTLS = false; })
|
||||
];
|
||||
packages.unix-time.postPatch = ''
|
||||
sed -i 's/mingwex//g' unix-time.cabal
|
||||
'';
|
||||
}];
|
||||
}).simplex-chat.components.exes.simplex-chat.override {
|
||||
postInstall = ''
|
||||
set -x
|
||||
${pkgs.tree}/bin/tree $out
|
||||
mkdir -p $out/_pkg
|
||||
cp $out/bin/* $out/_pkg
|
||||
${pkgs.tree}/bin/tree $out/_pkg
|
||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/${pkgs.pkgsCross.mingwW64.hostPlatform.system}-simplex-chat.zip *)
|
||||
rm -fR $out/_pkg
|
||||
mkdir -p $out/nix-support
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
"${pkgs.pkgsCross.mingwW64.hostPlatform.system}:lib:simplex-chat" = (drv' rec {
|
||||
pkgs' = pkgs.pkgsCross.mingwW64;
|
||||
extra-modules = [{
|
||||
packages.direct-sqlcipher.flags.openssl = true;
|
||||
# simd will try to read __cpu_model, which we don't expose
|
||||
# from the rts (yet!).
|
||||
packages.bitvec.flags.simd = false;
|
||||
packages.direct-sqlcipher.patches = [
|
||||
./scripts/nix/direct-sqlcipher-2.3.27-win.patch
|
||||
];
|
||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||
pkgs.pkgsCross.mingwW64.openssl
|
||||
];
|
||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||
pkgs.pkgsCross.mingwW64.openssl
|
||||
];
|
||||
packages.unix-time.postPatch = ''
|
||||
sed -i 's/mingwex//g' unix-time.cabal
|
||||
'';
|
||||
}];
|
||||
}).simplex-chat.components.library
|
||||
.override (p: {
|
||||
# enableShared = false;
|
||||
setupBuildFlags = p.component.setupBuildFlags ++ map (x: "--ghc-option=${x}") [
|
||||
"-shared"
|
||||
"-threaded"
|
||||
"-o" "libsimplex.dll"
|
||||
# "-optl-lHSrts_thr"
|
||||
"-optl-lffi"
|
||||
# "-optl-static-libgcc"
|
||||
# We can't do -optl-static-libstdc++ with gcc. g++ might
|
||||
# but then we are chaning the compiler altogether.
|
||||
"${./libsimplex.dll.def}"
|
||||
];
|
||||
postInstall = ''
|
||||
set -x
|
||||
function deps() {
|
||||
${pkgs.binutils}/bin/strings "$1" | grep '.\.dll'|grep -v -E 'Winsock|ADVAPI32|dbghelp|KERNEL32|msvcrt|ntdll|ole32|RPCRT4|SHELL32|USER32|WINMM|WS2_32|kernel32|GDI32'|grep -v "$1"
|
||||
}
|
||||
${pkgs.tree}/bin/tree $out
|
||||
mkdir -p $out/_pkg
|
||||
cp libsimplex.dll $out/_pkg
|
||||
cp libsimplex.dll.a $out/_pkg
|
||||
mkdir $out/libs
|
||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.openssl} -name "*.dll" -exec cp {} $out/libs \;
|
||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.libffi} -name "*.dll" -exec cp {} $out/libs \;
|
||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.gmp} -name "*.dll" -exec cp {} $out/libs \;
|
||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.stdenv.cc.cc} -name "*.dll" -exec cp {} $out/libs \;
|
||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.windows.mcfgthreads} -name "*.dll" -exec cp {} $out/libs \;
|
||||
|
||||
pushd $out/_pkg
|
||||
function copyDeps() {
|
||||
for dep in $(deps "$1"); do
|
||||
if [ ! -f "$dep" ]; then
|
||||
if [ ! -f ../libs/"$dep" ]; then
|
||||
echo "WARN: $1 -> $dep not found!"
|
||||
else
|
||||
cp ../libs/"$dep" .
|
||||
copyDeps "$dep"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
copyDeps libsimplex.dll
|
||||
popd
|
||||
${pkgs.tree}/bin/tree $out/_pkg
|
||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-${pkgs.pkgsCross.mingwW64.hostPlatform.system}-libsimplex.zip *)
|
||||
rm -fR $out/_pkg
|
||||
mkdir -p $out/nix-support
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
});
|
||||
# "${pkgs.pkgsCross.muslpi.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.muslpi).simplex-chat.components.exes.simplex-chat;
|
||||
|
||||
# STATIC aarch64-linux
|
||||
"${pkgs.pkgsCross.aarch64-multiplatform-musl.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.aarch64-multiplatform-musl).simplex-chat.components.exes.simplex-chat;
|
||||
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override {
|
||||
smallAddressSpace = true; enableShared = false;
|
||||
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
||||
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override (p: {
|
||||
smallAddressSpace = true;
|
||||
# we won't want -dyamic (see aarch64-android:lib:simplex-chat)
|
||||
enableShared = false;
|
||||
# we also do not want to have any dependencies listed (especially no rts!)
|
||||
enableStatic = false;
|
||||
|
||||
# This used to work with 8.10.7...
|
||||
# setupBuildFlags = p.component.setupBuildFlags ++ map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
||||
# ... but now with 9.6+
|
||||
# we have to do the -shared thing by hand.
|
||||
postBuild = ''
|
||||
armv7a-unknown-linux-androideabi-ghc -shared -o libsupport.so \
|
||||
-optl-Wl,-u,setLineBuffering \
|
||||
-optl-Wl,-u,pipe_std_to_socket \
|
||||
dist/build/*.a
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
|
||||
mkdir -p $out/_pkg
|
||||
@@ -138,14 +292,29 @@
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
"aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override {
|
||||
smallAddressSpace = true; enableShared = false;
|
||||
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
||||
});
|
||||
# The android-support package is at
|
||||
# https://github.com/simplex-chat/android-support
|
||||
"aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override (p: {
|
||||
smallAddressSpace = true;
|
||||
# no -dynamic
|
||||
enableShared = false;
|
||||
# but also no -staticlib
|
||||
enableStatic = false;
|
||||
|
||||
# we have to do the -shared thing by hand.
|
||||
postBuild = ''
|
||||
aarch64-unknown-linux-android-ghc -shared -o libsupport.so \
|
||||
-optl-Wl,-u,setLineBuffering \
|
||||
-optl-Wl,-u,pipe_std_to_socket \
|
||||
dist/build/*.a
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
|
||||
mkdir -p $out/_pkg
|
||||
cp libsupport.so $out/_pkg
|
||||
ls -lah $out/_pkg/*
|
||||
${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so
|
||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-aarch64-android-libsupport.zip *)
|
||||
rm -fR $out/_pkg
|
||||
@@ -154,10 +323,11 @@
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
});
|
||||
"armv7a-android:lib:simplex-chat" = (drv' {
|
||||
pkgs' = android32Pkgs;
|
||||
extra-modules = [{
|
||||
packages.text.flags.simdutf = false;
|
||||
packages.direct-sqlcipher.flags.openssl = true;
|
||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
||||
@@ -168,13 +338,56 @@
|
||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
||||
];
|
||||
# 32 bit patches
|
||||
packages.basement.patches = [
|
||||
./scripts/nix/basement-pr-573.patch
|
||||
];
|
||||
packages.memory.patches = [
|
||||
./scripts/nix/memory-pr-99.patch
|
||||
];
|
||||
}];
|
||||
}).simplex-chat.components.library.override {
|
||||
smallAddressSpace = true; enableShared = false;
|
||||
}).simplex-chat.components.library.override (p: {
|
||||
smallAddressSpace = true;
|
||||
# we want -shared, but not -dyanmic, hence `enableShared = false`.
|
||||
enableShared = false;
|
||||
# we _do_ want rts, and other libs. Hence `enableStatic = true`.
|
||||
enableStatic = true;
|
||||
# for android we build a shared library, passing these arguments is a bit tricky, as
|
||||
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
||||
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
||||
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"];
|
||||
setupBuildFlags = p.component.setupBuildFlags
|
||||
# flags to tell GHC we want to produce a -shared object, and we want to also link
|
||||
# - the ffi library (ffi)
|
||||
++ map (x: "--ghc-option=${x}") [
|
||||
"-shared" "-o" "libsimplex.so"
|
||||
"-threaded"
|
||||
# "-debug"
|
||||
"-optl-lffi"
|
||||
]
|
||||
# This is fairly idiotic. LLD will strip out foreign exported
|
||||
# symbols (a GHC bug? Codegen bug?). So we need to pass `-u <sym>`
|
||||
# to ensure they stay in the produced library. Having them
|
||||
# _undefined_ and _lazy_ (lld will tell with -y <sym> that the
|
||||
# symbol is lazy), makes them _defined_. m(
|
||||
++ map (sym: "--ghc-option=-optl-Wl,-u,${sym}") [
|
||||
"chat_close_store"
|
||||
"chat_decrypt_file"
|
||||
"chat_decrypt_media"
|
||||
"chat_encrypt_file"
|
||||
"chat_encrypt_media"
|
||||
"chat_migrate_init"
|
||||
"chat_parse_markdown"
|
||||
"chat_parse_server"
|
||||
"chat_password_hash"
|
||||
"chat_read_file"
|
||||
"chat_recv_msg"
|
||||
"chat_recv_msg_wait"
|
||||
"chat_send_cmd"
|
||||
"chat_send_remote_cmd"
|
||||
"chat_valid_name"
|
||||
"chat_json_length"
|
||||
"chat_write_file"
|
||||
];
|
||||
postInstall = ''
|
||||
set -x
|
||||
${pkgs.tree}/bin/tree $out
|
||||
@@ -218,10 +431,11 @@
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
});
|
||||
"aarch64-android:lib:simplex-chat" = (drv' {
|
||||
pkgs' = androidPkgs;
|
||||
extra-modules = [{
|
||||
packages.text.flags.simdutf = false;
|
||||
packages.direct-sqlcipher.flags.openssl = true;
|
||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||
(androidPkgs.openssl.override { static = true; })
|
||||
@@ -233,12 +447,50 @@
|
||||
(androidPkgs.openssl.override { static = true; })
|
||||
];
|
||||
}];
|
||||
}).simplex-chat.components.library.override {
|
||||
smallAddressSpace = true; enableShared = false;
|
||||
}).simplex-chat.components.library.override (p: {
|
||||
smallAddressSpace = true;
|
||||
# we do not want a dynamically linked object, even though we _do_
|
||||
# want to produce a _shared_ object. But `shared` implied -dyanmic
|
||||
# with cabal, so we disable and pass `-shared` explicitly.
|
||||
enableShared = false;
|
||||
# we do want static (e.g. pass all dependencies in, so we get -staticlib)
|
||||
enableStatic = true;
|
||||
# for android we build a shared library, passing these arguments is a bit tricky, as
|
||||
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
||||
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
||||
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"];
|
||||
setupBuildFlags = p.component.setupBuildFlags
|
||||
# flags to tell GHC we want to produce a -shared object, and we want to also link
|
||||
# - the ffi library (ffi)
|
||||
++ map (x: "--ghc-option=${x}") [
|
||||
"-shared" "-o" "libsimplex.so"
|
||||
"-threaded"
|
||||
# "-debug"
|
||||
"-optl-lffi"
|
||||
]
|
||||
# This is fairly idiotic. LLD will strip out foreign exported
|
||||
# symbols (a GHC bug? Codegen bug?). So we need to pass `-u <sym>`
|
||||
# to ensure they stay in the produced library. Having them
|
||||
# _undefined_ and _lazy_ (lld will tell with -y <sym> that the
|
||||
# symbol is lazy), makes them _defined_. m(
|
||||
++ map (sym: "--ghc-option=-optl-Wl,-u,${sym}") [
|
||||
"chat_close_store"
|
||||
"chat_decrypt_file"
|
||||
"chat_decrypt_media"
|
||||
"chat_encrypt_file"
|
||||
"chat_encrypt_media"
|
||||
"chat_migrate_init"
|
||||
"chat_parse_markdown"
|
||||
"chat_parse_server"
|
||||
"chat_password_hash"
|
||||
"chat_read_file"
|
||||
"chat_recv_msg"
|
||||
"chat_recv_msg_wait"
|
||||
"chat_send_cmd"
|
||||
"chat_send_remote_cmd"
|
||||
"chat_valid_name"
|
||||
"chat_json_length"
|
||||
"chat_write_file"
|
||||
];
|
||||
postInstall = ''
|
||||
set -x
|
||||
${pkgs.tree}/bin/tree $out
|
||||
@@ -282,7 +534,7 @@
|
||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
# builds for iOS and iOS simulator
|
||||
@@ -296,7 +548,8 @@
|
||||
packages.direct-sqlcipher.flags.commoncrypto = true;
|
||||
packages.entropy.flags.DoNotGetEntropy = true;
|
||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||
(pkgs.openssl.override { static = true; })
|
||||
# TODO: have a cross override for iOS, that sets this.
|
||||
((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;}))
|
||||
];
|
||||
}];
|
||||
}).simplex-chat.components.library.override (
|
||||
|
||||
+1
-6
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.6.1.0
|
||||
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.*
|
||||
@@ -151,7 +150,6 @@ tests:
|
||||
|
||||
ghc-options:
|
||||
# - -haddock
|
||||
- -O2
|
||||
- -Wall
|
||||
- -Wcompat
|
||||
- -Werror=incomplete-patterns
|
||||
@@ -159,6 +157,3 @@ ghc-options:
|
||||
- -Wincomplete-record-updates
|
||||
- -Wincomplete-uni-patterns
|
||||
- -Wunused-type-patterns
|
||||
|
||||
default-extensions:
|
||||
- StrictData
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
trap "rm apps/multiplatform/local.properties || true; rm local.properties || true; rm /tmp/simplex.keychain || true" EXIT
|
||||
trap "rm apps/multiplatform/local.properties 2> /dev/null || true; rm local.properties 2> /dev/null || true; rm /tmp/simplex.keychain" EXIT
|
||||
echo "desktop.mac.signing.identity=Developer ID Application: SimpleX Chat Ltd (5NN7GUYB6T)" >> apps/multiplatform/local.properties
|
||||
echo "desktop.mac.signing.keychain=/tmp/simplex.keychain" >> apps/multiplatform/local.properties
|
||||
echo "desktop.mac.notarization.apple_id=$APPLE_SIMPLEX_NOTARIZATION_APPLE_ID" >> apps/multiplatform/local.properties
|
||||
@@ -10,6 +10,10 @@ echo "desktop.mac.notarization.password=$APPLE_SIMPLEX_NOTARIZATION_PASSWORD" >>
|
||||
echo "desktop.mac.notarization.team_id=5NN7GUYB6T" >> apps/multiplatform/local.properties
|
||||
echo "$APPLE_SIMPLEX_SIGNING_KEYCHAIN" | base64 --decode - > /tmp/simplex.keychain
|
||||
|
||||
security unlock-keychain -p "" /tmp/simplex.keychain
|
||||
# Adding keychain to the list of keychains.
|
||||
# Otherwise, it can find cert but exits while signing with "error: The specified item could not be found in the keychain."
|
||||
security list-keychains -s `security list-keychains | xargs` /tmp/simplex.keychain
|
||||
scripts/desktop/build-lib-mac.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageDmg
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
security create-keychain -p "" simplex.keychain
|
||||
security set-keychain-settings -u simplex.keychain
|
||||
security add-certificates -k simplex.keychain "Developer ID Application: SimpleX Chat Ltd (5NN7GUYB6T).cer"
|
||||
security add-certificates -k simplex.keychain "Developer ID Certification Authority.cer"
|
||||
# Private key with access from any app
|
||||
security import "SimpleX Chat.p12" -P "" -k simplex.keychain -A
|
||||
# Public key
|
||||
security import "SimpleX Chat.pem" -k simplex.keychain
|
||||
@@ -8,7 +8,7 @@ function readlink() {
|
||||
|
||||
OS=linux
|
||||
ARCH=${1:-`uname -a | rev | cut -d' ' -f2 | rev`}
|
||||
GHC_VERSION=8.10.7
|
||||
GHC_VERSION=9.6.3
|
||||
|
||||
if [ "$ARCH" == "aarch64" ]; then
|
||||
COMPOSE_ARCH=arm64
|
||||
@@ -25,7 +25,7 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc
|
||||
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
||||
|
||||
rm -rf $BUILD_DIR
|
||||
cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN' --ghc-options="-optl-L$(ghc --print-libdir)/rts -optl-Wl,--as-needed,-lHSrts_thr-ghc$GHC_VERSION"
|
||||
cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded'
|
||||
cd $BUILD_DIR/build
|
||||
#patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
||||
#patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
||||
|
||||
@@ -5,13 +5,14 @@ set -e
|
||||
OS=mac
|
||||
ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}"
|
||||
COMPOSE_ARCH=$ARCH
|
||||
GHC_VERSION=8.10.7
|
||||
GHC_VERSION=9.6.3
|
||||
|
||||
if [ "$ARCH" == "arm64" ]; then
|
||||
ARCH=aarch64
|
||||
else
|
||||
COMPOSE_ARCH=x64
|
||||
fi
|
||||
|
||||
LIB_EXT=dylib
|
||||
LIB=libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT
|
||||
GHC_LIBS_DIR=$(ghc --print-libdir)
|
||||
@@ -23,13 +24,26 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc
|
||||
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
||||
|
||||
rm -rf $BUILD_DIR
|
||||
cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/rts -optl-lHSrts_thr-ghc8.10.7 -optl-lffi"
|
||||
cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi"
|
||||
|
||||
cd $BUILD_DIR/build
|
||||
mkdir deps 2> /dev/null || true
|
||||
|
||||
# It's not included by default for some reason. Compiled lib tries to find system one but it's not always available
|
||||
cp $GHC_LIBS_DIR/rts/libffi.dylib ./deps
|
||||
#cp $GHC_LIBS_DIR/libffi.dylib ./deps
|
||||
(
|
||||
BUILD=$PWD
|
||||
cp /tmp/libffi-3.4.4/*-apple-darwin*/.libs/libffi.dylib $BUILD/deps || \
|
||||
( \
|
||||
cd /tmp && \
|
||||
curl --tlsv1.2 "https://gitlab.haskell.org/ghc/libffi-tarballs/-/raw/libffi-3.4.4/libffi-3.4.4.tar.gz?inline=false" -o libffi.tar.gz && \
|
||||
tar -xzvf libffi.tar.gz && \
|
||||
cd "libffi-3.4.4" && \
|
||||
./configure && \
|
||||
make && \
|
||||
cp *-apple-darwin*/.libs/libffi.dylib $BUILD/deps \
|
||||
)
|
||||
)
|
||||
|
||||
DYLIBS=`otool -L $LIB | grep @rpath | tail -n +2 | cut -d' ' -f 1 | cut -d'/' -f2`
|
||||
RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11`
|
||||
@@ -70,6 +84,8 @@ function copy_deps() {
|
||||
}
|
||||
|
||||
copy_deps $LIB
|
||||
# Special case
|
||||
cp $(ghc --print-libdir)/$ARCH-osx-ghc-$GHC_VERSION/libHSghc-boot-th-$GHC_VERSION-ghc$GHC_VERSION.dylib deps
|
||||
rm deps/`basename $LIB`
|
||||
|
||||
cd -
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
From 38be2c93acb6f459d24ed6c626981c35ccf44095 Mon Sep 17 00:00:00 2001
|
||||
From: Sylvain Henry <sylvain@haskus.fr>
|
||||
Date: Thu, 16 Feb 2023 15:40:45 +0100
|
||||
Subject: [PATCH] Fix build on 32-bit architectures
|
||||
|
||||
---
|
||||
Basement/Bits.hs | 4 ++++
|
||||
Basement/From.hs | 24 -----------------------
|
||||
Basement/Numerical/Additive.hs | 4 ++++
|
||||
Basement/Numerical/Conversion.hs | 20 +++++++++++++++++++
|
||||
Basement/PrimType.hs | 6 +++++-
|
||||
Basement/Types/OffsetSize.hs | 22 +++++++++++++++++++--
|
||||
6 files changed, 53 insertions(+), 27 deletions(-)
|
||||
|
||||
diff --git a/Basement/Bits.hs b/Basement/Bits.hs
|
||||
index 7eeea0f5..24520ed7 100644
|
||||
--- a/Basement/Bits.hs
|
||||
+++ b/Basement/Bits.hs
|
||||
@@ -54,8 +54,12 @@ import GHC.Int
|
||||
import Basement.Compat.Primitive
|
||||
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+import GHC.Exts
|
||||
+#else
|
||||
import GHC.IntWord64
|
||||
#endif
|
||||
+#endif
|
||||
|
||||
-- | operation over finite bits
|
||||
class FiniteBitsOps bits where
|
||||
diff --git a/Basement/From.hs b/Basement/From.hs
|
||||
index 7bbe141c..80014b3e 100644
|
||||
--- a/Basement/From.hs
|
||||
+++ b/Basement/From.hs
|
||||
@@ -272,23 +272,11 @@ instance (NatWithinBound (CountOf ty) n, KnownNat n, PrimType ty)
|
||||
tryFrom = BlockN.toBlockN . UArray.toBlock . BoxArray.mapToUnboxed id
|
||||
|
||||
instance (KnownNat n, NatWithinBound Word8 n) => From (Zn64 n) Word8 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . unZn64 where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . unZn64 where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# w))
|
||||
-#endif
|
||||
instance (KnownNat n, NatWithinBound Word16 n) => From (Zn64 n) Word16 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . unZn64 where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . unZn64 where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# w))
|
||||
-#endif
|
||||
instance (KnownNat n, NatWithinBound Word32 n) => From (Zn64 n) Word32 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . unZn64 where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . unZn64 where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# w))
|
||||
-#endif
|
||||
instance From (Zn64 n) Word64 where
|
||||
from = unZn64
|
||||
instance From (Zn64 n) Word128 where
|
||||
@@ -297,23 +285,11 @@ instance From (Zn64 n) Word256 where
|
||||
from = from . unZn64
|
||||
|
||||
instance (KnownNat n, NatWithinBound Word8 n) => From (Zn n) Word8 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# w))
|
||||
-#endif
|
||||
instance (KnownNat n, NatWithinBound Word16 n) => From (Zn n) Word16 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# w))
|
||||
-#endif
|
||||
instance (KnownNat n, NatWithinBound Word32 n) => From (Zn n) Word32 where
|
||||
-#if __GLASGOW_HASKELL__ >= 904
|
||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
||||
-#else
|
||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# w))
|
||||
-#endif
|
||||
instance (KnownNat n, NatWithinBound Word64 n) => From (Zn n) Word64 where
|
||||
from = naturalToWord64 . unZn
|
||||
instance (KnownNat n, NatWithinBound Word128 n) => From (Zn n) Word128 where
|
||||
diff --git a/Basement/Numerical/Additive.hs b/Basement/Numerical/Additive.hs
|
||||
index d0dfb973..8ab65aa0 100644
|
||||
--- a/Basement/Numerical/Additive.hs
|
||||
+++ b/Basement/Numerical/Additive.hs
|
||||
@@ -30,8 +30,12 @@ import qualified Basement.Types.Word128 as Word128
|
||||
import qualified Basement.Types.Word256 as Word256
|
||||
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+import GHC.Exts
|
||||
+#else
|
||||
import GHC.IntWord64
|
||||
#endif
|
||||
+#endif
|
||||
|
||||
-- | Represent class of things that can be added together,
|
||||
-- contains a neutral element and is commutative.
|
||||
diff --git a/Basement/Numerical/Conversion.hs b/Basement/Numerical/Conversion.hs
|
||||
index db502c07..fddc8232 100644
|
||||
--- a/Basement/Numerical/Conversion.hs
|
||||
+++ b/Basement/Numerical/Conversion.hs
|
||||
@@ -26,8 +26,12 @@ import GHC.Word
|
||||
import Basement.Compat.Primitive
|
||||
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+import GHC.Exts
|
||||
+#else
|
||||
import GHC.IntWord64
|
||||
#endif
|
||||
+#endif
|
||||
|
||||
intToInt64 :: Int -> Int64
|
||||
#if WORD_SIZE_IN_BITS == 64
|
||||
@@ -96,11 +100,22 @@ int64ToWord64 (I64# i) = W64# (int64ToWord64# i)
|
||||
#endif
|
||||
|
||||
#if WORD_SIZE_IN_BITS == 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+word64ToWord# :: Word64# -> Word#
|
||||
+word64ToWord# i = word64ToWord# i
|
||||
+#else
|
||||
word64ToWord# :: Word# -> Word#
|
||||
word64ToWord# i = i
|
||||
+#endif
|
||||
{-# INLINE word64ToWord# #-}
|
||||
#endif
|
||||
|
||||
+#if WORD_SIZE_IN_BITS < 64
|
||||
+word64ToWord32# :: Word64# -> Word32#
|
||||
+word64ToWord32# i = wordToWord32# (word64ToWord# i)
|
||||
+{-# INLINE word64ToWord32# #-}
|
||||
+#endif
|
||||
+
|
||||
-- | 2 Word32s
|
||||
data Word32x2 = Word32x2 {-# UNPACK #-} !Word32
|
||||
{-# UNPACK #-} !Word32
|
||||
@@ -113,9 +128,14 @@ word64ToWord32s (W64# w64) = Word32x2 (W32# (wordToWord32# (uncheckedShiftRL# (G
|
||||
word64ToWord32s (W64# w64) = Word32x2 (W32# (wordToWord32# (uncheckedShiftRL# w64 32#))) (W32# (wordToWord32# w64))
|
||||
#endif
|
||||
#else
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+word64ToWord32s :: Word64 -> Word32x2
|
||||
+word64ToWord32s (W64# w64) = Word32x2 (W32# (word64ToWord32# (uncheckedShiftRL64# w64 32#))) (W32# (word64ToWord32# w64))
|
||||
+#else
|
||||
word64ToWord32s :: Word64 -> Word32x2
|
||||
word64ToWord32s (W64# w64) = Word32x2 (W32# (word64ToWord# (uncheckedShiftRL64# w64 32#))) (W32# (word64ToWord# w64))
|
||||
#endif
|
||||
+#endif
|
||||
|
||||
wordToChar :: Word -> Char
|
||||
wordToChar (W# word) = C# (chr# (word2Int# word))
|
||||
diff --git a/Basement/PrimType.hs b/Basement/PrimType.hs
|
||||
index f8ca2926..a888ec91 100644
|
||||
--- a/Basement/PrimType.hs
|
||||
+++ b/Basement/PrimType.hs
|
||||
@@ -54,7 +54,11 @@ import Basement.Nat
|
||||
import qualified Prelude (quot)
|
||||
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
-import GHC.IntWord64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+import GHC.Exts
|
||||
+#else
|
||||
+import GHC.IntWord64
|
||||
+#endif
|
||||
#endif
|
||||
|
||||
#ifdef FOUNDATION_BOUNDS_CHECK
|
||||
diff --git a/Basement/Types/OffsetSize.hs b/Basement/Types/OffsetSize.hs
|
||||
index cd944927..1ea80dad 100644
|
||||
--- a/Basement/Types/OffsetSize.hs
|
||||
+++ b/Basement/Types/OffsetSize.hs
|
||||
@@ -70,8 +70,12 @@ import Data.List (foldl')
|
||||
import qualified Prelude
|
||||
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+import GHC.Exts
|
||||
+#else
|
||||
import GHC.IntWord64
|
||||
#endif
|
||||
+#endif
|
||||
|
||||
-- | File size in bytes
|
||||
newtype FileSize = FileSize Word64
|
||||
@@ -225,20 +229,26 @@ countOfRoundUp alignment (CountOf n) = CountOf ((n + (alignment-1)) .&. compleme
|
||||
|
||||
csizeOfSize :: CountOf Word8 -> CSize
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+csizeOfSize (CountOf (I# sz)) = CSize (W32# (wordToWord32# (int2Word# sz)))
|
||||
+#else
|
||||
csizeOfSize (CountOf (I# sz)) = CSize (W32# (int2Word# sz))
|
||||
+#endif
|
||||
#else
|
||||
#if __GLASGOW_HASKELL__ >= 904
|
||||
csizeOfSize (CountOf (I# sz)) = CSize (W64# (wordToWord64# (int2Word# sz)))
|
||||
-
|
||||
#else
|
||||
csizeOfSize (CountOf (I# sz)) = CSize (W64# (int2Word# sz))
|
||||
-
|
||||
#endif
|
||||
#endif
|
||||
|
||||
csizeOfOffset :: Offset8 -> CSize
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+csizeOfOffset (Offset (I# sz)) = CSize (W32# (wordToWord32# (int2Word# sz)))
|
||||
+#else
|
||||
csizeOfOffset (Offset (I# sz)) = CSize (W32# (int2Word# sz))
|
||||
+#endif
|
||||
#else
|
||||
#if __GLASGOW_HASKELL__ >= 904
|
||||
csizeOfOffset (Offset (I# sz)) = CSize (W64# (wordToWord64# (int2Word# sz)))
|
||||
@@ -250,7 +260,11 @@ csizeOfOffset (Offset (I# sz)) = CSize (W64# (int2Word# sz))
|
||||
sizeOfCSSize :: CSsize -> CountOf Word8
|
||||
sizeOfCSSize (CSsize (-1)) = error "invalid size: CSSize is -1"
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+sizeOfCSSize (CSsize (I32# sz)) = CountOf (I# (int32ToInt# sz))
|
||||
+#else
|
||||
sizeOfCSSize (CSsize (I32# sz)) = CountOf (I# sz)
|
||||
+#endif
|
||||
#else
|
||||
#if __GLASGOW_HASKELL__ >= 904
|
||||
sizeOfCSSize (CSsize (I64# sz)) = CountOf (I# (int64ToInt# sz))
|
||||
@@ -261,7 +275,11 @@ sizeOfCSSize (CSsize (I64# sz)) = CountOf (I# sz)
|
||||
|
||||
sizeOfCSize :: CSize -> CountOf Word8
|
||||
#if WORD_SIZE_IN_BITS < 64
|
||||
+#if __GLASGOW_HASKELL__ >= 904
|
||||
+sizeOfCSize (CSize (W32# sz)) = CountOf (I# (word2Int# (word32ToWord# sz)))
|
||||
+#else
|
||||
sizeOfCSize (CSize (W32# sz)) = CountOf (I# (word2Int# sz))
|
||||
+#endif
|
||||
#else
|
||||
#if __GLASGOW_HASKELL__ >= 904
|
||||
sizeOfCSize (CSize (W64# sz)) = CountOf (I# (word2Int# (word64ToWord# sz)))
|
||||
@@ -0,0 +1,12 @@
|
||||
diff --git a/direct-sqlcipher.cabal b/direct-sqlcipher.cabal
|
||||
index 728ba3e..c63745e 100644
|
||||
--- a/direct-sqlcipher.cabal
|
||||
+++ b/direct-sqlcipher.cabal
|
||||
@@ -84,6 +84,8 @@ library
|
||||
cc-options: -DSQLITE_TEMP_STORE=2
|
||||
-DSQLITE_HAS_CODEC
|
||||
|
||||
+ extra-libraries: ws2_32
|
||||
+
|
||||
if !os(windows) && !os(android)
|
||||
extra-libraries: pthread
|
||||
@@ -0,0 +1,36 @@
|
||||
From 2738929ce15b4c8704bbbac24a08539b5d4bf30e Mon Sep 17 00:00:00 2001
|
||||
From: sternenseemann <sternenseemann@systemli.org>
|
||||
Date: Mon, 14 Aug 2023 10:51:30 +0200
|
||||
Subject: [PATCH] Data.Memory.Internal.CompatPrim64: fix 32 bit with GHC >= 9.4
|
||||
|
||||
Since 9.4, GHC.Prim exports Word64# operations like timesWord64# even on
|
||||
i686 whereas GHC.IntWord64 no longer exists. Therefore, we can just use
|
||||
the ready made solution.
|
||||
|
||||
Closes #98, as it should be the better solution.
|
||||
---
|
||||
Data/Memory/Internal/CompatPrim64.hs | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/Data/Memory/Internal/CompatPrim64.hs b/Data/Memory/Internal/CompatPrim64.hs
|
||||
index b9eef8a..a134c88 100644
|
||||
--- a/Data/Memory/Internal/CompatPrim64.hs
|
||||
+++ b/Data/Memory/Internal/CompatPrim64.hs
|
||||
@@ -150,6 +150,7 @@ w64# :: Word# -> Word# -> Word# -> Word64#
|
||||
w64# w _ _ = w
|
||||
|
||||
#elif WORD_SIZE_IN_BITS == 32
|
||||
+#if __GLASGOW_HASKELL__ < 904
|
||||
import GHC.IntWord64
|
||||
import GHC.Prim (Word#)
|
||||
|
||||
@@ -158,6 +159,9 @@ timesWord64# a b =
|
||||
let !ai = word64ToInt64# a
|
||||
!bi = word64ToInt64# b
|
||||
in int64ToWord64# (timesInt64# ai bi)
|
||||
+#else
|
||||
+import GHC.Prim
|
||||
+#endif
|
||||
|
||||
w64# :: Word# -> Word# -> Word# -> Word64#
|
||||
w64# _ hw lw =
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."1e6268cc1dbba69639425f7d5a6c9a07995e0bb2" = "14pvy7vivvigxj876kwikxcy7c2jc30azwydfgvnlj4xwcclil8q";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."ee90ea6a69fe8283d37d9821cd83798fd0a76260" = "0my9f4dlfa79yq73rys0m2zb61fd9bp65djvavk6jwy6qzl5vr40";
|
||||
"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
-31
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.6.1.0
|
||||
version: 5.6.0.4
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -139,7 +139,6 @@ library
|
||||
Simplex.Chat.Migrations.M20240226_users_restrict
|
||||
Simplex.Chat.Migrations.M20240228_pq
|
||||
Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id
|
||||
Simplex.Chat.Migrations.M20240324_custom_data
|
||||
Simplex.Chat.Mobile
|
||||
Simplex.Chat.Mobile.File
|
||||
Simplex.Chat.Mobile.Shared
|
||||
@@ -169,7 +168,6 @@ library
|
||||
Simplex.Chat.Styled
|
||||
Simplex.Chat.Terminal
|
||||
Simplex.Chat.Terminal.Input
|
||||
Simplex.Chat.Terminal.Main
|
||||
Simplex.Chat.Terminal.Notification
|
||||
Simplex.Chat.Terminal.Output
|
||||
Simplex.Chat.Types
|
||||
@@ -181,16 +179,13 @@ library
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns
|
||||
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.*
|
||||
@@ -243,16 +238,13 @@ executable simplex-bot
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -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.*
|
||||
@@ -306,16 +298,13 @@ executable simplex-bot-advanced
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -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.*
|
||||
@@ -368,20 +357,17 @@ executable simplex-broadcast-bot
|
||||
hs-source-dirs:
|
||||
apps/simplex-broadcast-bot
|
||||
apps/simplex-broadcast-bot/src
|
||||
default-extensions:
|
||||
StrictData
|
||||
other-modules:
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -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.*
|
||||
@@ -436,16 +422,13 @@ executable simplex-chat
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -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.*
|
||||
@@ -499,8 +482,6 @@ executable simplex-directory-service
|
||||
hs-source-dirs:
|
||||
apps/simplex-directory-service
|
||||
apps/simplex-directory-service/src
|
||||
default-extensions:
|
||||
StrictData
|
||||
other-modules:
|
||||
Directory.Events
|
||||
Directory.Options
|
||||
@@ -508,14 +489,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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -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.*
|
||||
@@ -600,9 +580,7 @@ test-suite simplex-chat-test
|
||||
tests
|
||||
apps/simplex-broadcast-bot/src
|
||||
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: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
|
||||
build-depends:
|
||||
QuickCheck ==2.14.*
|
||||
, aeson ==2.2.*
|
||||
@@ -610,7 +588,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.*
|
||||
|
||||
+491
-504
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -44,7 +44,7 @@ archiveChatDbFile = "simplex_v1_chat.db"
|
||||
archiveFilesFolder :: String
|
||||
archiveFilesFolder = "simplex_v1_files"
|
||||
|
||||
exportArchive :: ArchiveConfig -> CM' ()
|
||||
exportArchive :: ChatMonad m => ArchiveConfig -> m ()
|
||||
exportArchive cfg@ArchiveConfig {archivePath, disableCompression} =
|
||||
withTempDir cfg "simplex-chat." $ \dir -> do
|
||||
StorageFiles {chatStore, agentStore, filesPath} <- storageFiles
|
||||
@@ -55,7 +55,7 @@ exportArchive cfg@ArchiveConfig {archivePath, disableCompression} =
|
||||
let method = if disableCompression == Just True then Z.Store else Z.Deflate
|
||||
Z.createArchive archivePath $ Z.packDirRecur method Z.mkEntrySelector dir
|
||||
|
||||
importArchive :: ArchiveConfig -> CM' [ArchiveError]
|
||||
importArchive :: ChatMonad m => ArchiveConfig -> m [ArchiveError]
|
||||
importArchive cfg@ArchiveConfig {archivePath} =
|
||||
withTempDir cfg "simplex-chat." $ \dir -> do
|
||||
Z.withArchive archivePath $ Z.unpackInto dir
|
||||
@@ -78,12 +78,12 @@ importArchive cfg@ArchiveConfig {archivePath} =
|
||||
(pure [])
|
||||
_ -> pure []
|
||||
|
||||
withTempDir :: ArchiveConfig -> (String -> (FilePath -> CM' a) -> CM' a)
|
||||
withTempDir :: ChatMonad m => ArchiveConfig -> (String -> (FilePath -> m a) -> m a)
|
||||
withTempDir cfg = case parentTempDirectory (cfg :: ArchiveConfig) of
|
||||
Just tmpDir -> withTempDirectory tmpDir
|
||||
_ -> withSystemTempDirectory
|
||||
|
||||
copyDirectoryFiles :: FilePath -> FilePath -> CM' [ArchiveError]
|
||||
copyDirectoryFiles :: ChatMonad m => FilePath -> FilePath -> m [ArchiveError]
|
||||
copyDirectoryFiles fromDir toDir = do
|
||||
createDirectoryIfMissing False toDir
|
||||
fs <- listDirectory fromDir
|
||||
@@ -97,9 +97,9 @@ copyDirectoryFiles fromDir toDir = do
|
||||
f' = fromDir </> fn
|
||||
whenM (doesFileExist f') $ copyFile f' $ toDir </> fn
|
||||
|
||||
deleteStorage :: CM ()
|
||||
deleteStorage :: ChatMonad m => m ()
|
||||
deleteStorage = do
|
||||
fs <- lift storageFiles
|
||||
fs <- storageFiles
|
||||
liftIO $ closeSQLiteStore `withStores` fs
|
||||
remove `withDBs` fs
|
||||
mapM_ removeDir $ filesPath fs
|
||||
@@ -114,17 +114,17 @@ data StorageFiles = StorageFiles
|
||||
filesPath :: Maybe FilePath
|
||||
}
|
||||
|
||||
storageFiles :: CM' StorageFiles
|
||||
storageFiles :: ChatMonad m => m StorageFiles
|
||||
storageFiles = do
|
||||
ChatController {chatStore, filesFolder, smpAgent} <- ask
|
||||
let agentStore = agentClientStore smpAgent
|
||||
filesPath <- readTVarIO filesFolder
|
||||
pure StorageFiles {chatStore, agentStore, filesPath}
|
||||
|
||||
sqlCipherExport :: DBEncryptionConfig -> CM ()
|
||||
sqlCipherExport :: forall m. ChatMonad m => DBEncryptionConfig -> m ()
|
||||
sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = DBEncryptionKey key', keepKey} =
|
||||
when (key /= key') $ do
|
||||
fs <- lift storageFiles
|
||||
fs <- storageFiles
|
||||
checkFile `withDBs` fs
|
||||
backup `withDBs` fs
|
||||
checkEncryption `withStores` fs
|
||||
@@ -159,7 +159,7 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D
|
||||
"DETACH DATABASE exported;"
|
||||
]
|
||||
|
||||
withDB :: FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> CM ()
|
||||
withDB :: forall a m. ChatMonad m => FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> m ()
|
||||
withDB f' a err =
|
||||
liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing)
|
||||
`catch` checkSQLError
|
||||
@@ -169,7 +169,7 @@ withDB f' a err =
|
||||
checkSQLError e = case SQL.sqlError e of
|
||||
SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase
|
||||
_ -> sqliteError' e
|
||||
sqliteError' :: Show e => e -> CM (Maybe SQLiteError)
|
||||
sqliteError' :: Show e => e -> m (Maybe SQLiteError)
|
||||
sqliteError' = pure . Just . SQLiteError . show
|
||||
|
||||
testSQL :: BA.ScrubbedBytes -> Text
|
||||
@@ -184,9 +184,9 @@ testSQL k =
|
||||
keySQL :: BA.ScrubbedBytes -> [Text]
|
||||
keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)]
|
||||
|
||||
sqlCipherTestKey :: DBEncryptionKey -> CM ()
|
||||
sqlCipherTestKey :: forall m. ChatMonad m => DBEncryptionKey -> m ()
|
||||
sqlCipherTestKey (DBEncryptionKey key) = do
|
||||
fs <- lift storageFiles
|
||||
fs <- storageFiles
|
||||
testKey `withDBs` fs
|
||||
where
|
||||
testKey f = withDB f (`SQL.exec` testSQL key) DBErrorOpen
|
||||
|
||||
@@ -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)
|
||||
@@ -83,7 +82,7 @@ import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), Cor
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import Simplex.Messaging.Transport (TLS, simplexMQVersion)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost)
|
||||
import Simplex.Messaging.Util (allFinally, catchAllErrors, catchAllErrors', tryAllErrors, tryAllErrors', (<$$>))
|
||||
import Simplex.Messaging.Util (allFinally, catchAllErrors, liftIOEither, tryAllErrors, (<$$>))
|
||||
import Simplex.RemoteControl.Client
|
||||
import Simplex.RemoteControl.Invitation (RCSignedInvitation, RCVerifiedInvitation)
|
||||
import Simplex.RemoteControl.Types
|
||||
@@ -145,28 +144,9 @@ data ChatConfig = ChatConfig
|
||||
ciExpirationInterval :: Int64, -- microseconds
|
||||
coreApi :: Bool,
|
||||
highlyAvailable :: Bool,
|
||||
deviceNameForRemote :: Text,
|
||||
chatHooks :: ChatHooks
|
||||
deviceNameForRemote :: Text
|
||||
}
|
||||
|
||||
-- The hooks can be used to extend or customize chat core in mobile or CLI clients.
|
||||
data ChatHooks = ChatHooks
|
||||
{ -- preCmdHook can be used to process or modify the commands before they are processed.
|
||||
-- This hook should be used to process CustomChatCommand.
|
||||
-- if this hook returns ChatResponse, the command processing will be skipped.
|
||||
preCmdHook :: ChatController -> ChatCommand -> IO (Either ChatResponse ChatCommand),
|
||||
-- eventHook can be used to additionally process or modify events,
|
||||
-- it is called before the event is sent to the user (or to the UI).
|
||||
eventHook :: ChatController -> ChatResponse -> IO ChatResponse
|
||||
}
|
||||
|
||||
defaultChatHooks :: ChatHooks
|
||||
defaultChatHooks =
|
||||
ChatHooks
|
||||
{ preCmdHook = \_ -> pure . Right,
|
||||
eventHook = \_ -> pure
|
||||
}
|
||||
|
||||
data DefaultAgentServers = DefaultAgentServers
|
||||
{ smp :: NonEmpty SMPServerWithAuth,
|
||||
ntf :: [NtfServer],
|
||||
@@ -491,9 +471,6 @@ data ChatCommand
|
||||
| GetAgentSubsDetails
|
||||
| GetAgentWorkers
|
||||
| GetAgentWorkersDetails
|
||||
-- The parser will return this command for strings that start from "//".
|
||||
-- This command should be processed in preCmdHook
|
||||
| CustomChatCommand ByteString
|
||||
deriving (Show)
|
||||
|
||||
allowRemoteCommand :: ChatCommand -> Bool -- XXX: consider using Relay/Block/ForceLocal
|
||||
@@ -620,9 +597,10 @@ data ChatResponse
|
||||
| CRContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
||||
| CRLeftMemberUser {user :: User, groupInfo :: GroupInfo}
|
||||
| CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo}
|
||||
| CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr}
|
||||
| CRRcvFileDescrReady {user :: User, chatItem :: AChatItem}
|
||||
| CRRcvFileAccepted {user :: User, chatItem :: AChatItem}
|
||||
| CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer}
|
||||
| CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem}
|
||||
| CRStandaloneFileInfo {fileMeta :: Maybe J.Value}
|
||||
| CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download
|
||||
| CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats
|
||||
@@ -748,7 +726,6 @@ data ChatResponse
|
||||
| CRArchiveImported {archiveErrors :: [ArchiveError]}
|
||||
| CRAppSettings {appSettings :: AppSettings}
|
||||
| CRTimedAction {action :: String, durationMilliseconds :: Int64}
|
||||
| CRCustomChatResponse {user_ :: Maybe User, response :: Text}
|
||||
deriving (Show)
|
||||
|
||||
-- some of these can only be used as command responses
|
||||
@@ -1141,7 +1118,7 @@ data DatabaseError
|
||||
data SQLiteError = SQLiteErrorNotADatabase | SQLiteError String
|
||||
deriving (Show, Exception)
|
||||
|
||||
throwDBError :: DatabaseError -> CM ()
|
||||
throwDBError :: ChatMonad m => DatabaseError -> m ()
|
||||
throwDBError = throwError . ChatErrorDatabase
|
||||
|
||||
-- TODO review errors, some of it can be covered by HTTP2 errors
|
||||
@@ -1245,59 +1222,39 @@ data RemoteCtrlInfo = RemoteCtrlInfo
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
type CM' a = ReaderT ChatController IO a
|
||||
type ChatMonad' m = (MonadUnliftIO m, MonadReader ChatController m)
|
||||
|
||||
type CM a = ExceptT ChatError (ReaderT ChatController IO) a
|
||||
type ChatMonad m = (ChatMonad' m, MonadError ChatError m)
|
||||
|
||||
chatReadVar :: (ChatController -> TVar a) -> CM a
|
||||
chatReadVar = lift . chatReadVar'
|
||||
chatReadVar :: ChatMonad' m => (ChatController -> TVar a) -> m a
|
||||
chatReadVar f = asks f >>= readTVarIO
|
||||
{-# INLINE chatReadVar #-}
|
||||
|
||||
chatReadVar' :: (ChatController -> TVar a) -> CM' a
|
||||
chatReadVar' f = asks f >>= readTVarIO
|
||||
{-# INLINE chatReadVar' #-}
|
||||
|
||||
chatWriteVar :: (ChatController -> TVar a) -> a -> CM ()
|
||||
chatWriteVar f = lift . chatWriteVar' f
|
||||
chatWriteVar :: ChatMonad' m => (ChatController -> TVar a) -> a -> m ()
|
||||
chatWriteVar f value = asks f >>= atomically . (`writeTVar` value)
|
||||
{-# INLINE chatWriteVar #-}
|
||||
|
||||
chatWriteVar' :: (ChatController -> TVar a) -> a -> CM' ()
|
||||
chatWriteVar' f value = asks f >>= atomically . (`writeTVar` value)
|
||||
{-# INLINE chatWriteVar' #-}
|
||||
|
||||
chatModifyVar :: (ChatController -> TVar a) -> (a -> a) -> CM ()
|
||||
chatModifyVar f = lift . chatModifyVar' f
|
||||
chatModifyVar :: ChatMonad' m => (ChatController -> TVar a) -> (a -> a) -> m ()
|
||||
chatModifyVar f newValue = asks f >>= atomically . (`modifyTVar'` newValue)
|
||||
{-# INLINE chatModifyVar #-}
|
||||
|
||||
chatModifyVar' :: (ChatController -> TVar a) -> (a -> a) -> CM' ()
|
||||
chatModifyVar' f newValue = asks f >>= atomically . (`modifyTVar'` newValue)
|
||||
{-# INLINE chatModifyVar' #-}
|
||||
|
||||
setContactNetworkStatus :: Contact -> NetworkStatus -> CM' ()
|
||||
setContactNetworkStatus :: ChatMonad' m => Contact -> NetworkStatus -> m ()
|
||||
setContactNetworkStatus Contact {activeConn = Nothing} _ = pure ()
|
||||
setContactNetworkStatus Contact {activeConn = Just Connection {agentConnId}} status = chatModifyVar' connNetworkStatuses $ M.insert agentConnId status
|
||||
setContactNetworkStatus Contact {activeConn = Just Connection {agentConnId}} status = chatModifyVar connNetworkStatuses $ M.insert agentConnId status
|
||||
|
||||
tryChatError :: CM a -> CM (Either ChatError a)
|
||||
tryChatError :: ChatMonad m => m a -> m (Either ChatError a)
|
||||
tryChatError = tryAllErrors mkChatError
|
||||
{-# INLINE tryChatError #-}
|
||||
|
||||
tryChatError' :: CM a -> CM' (Either ChatError a)
|
||||
tryChatError' = tryAllErrors' mkChatError
|
||||
{-# INLINE tryChatError' #-}
|
||||
|
||||
catchChatError :: CM a -> (ChatError -> CM a) -> CM a
|
||||
catchChatError :: ChatMonad m => m a -> (ChatError -> m a) -> m a
|
||||
catchChatError = catchAllErrors mkChatError
|
||||
{-# INLINE catchChatError #-}
|
||||
|
||||
catchChatError' :: CM a -> (ChatError -> CM' a) -> CM' a
|
||||
catchChatError' = catchAllErrors' mkChatError
|
||||
{-# INLINE catchChatError' #-}
|
||||
|
||||
chatFinally :: CM a -> CM b -> CM a
|
||||
chatFinally :: ChatMonad m => m a -> m b -> m a
|
||||
chatFinally = allFinally mkChatError
|
||||
{-# INLINE chatFinally #-}
|
||||
|
||||
onChatError :: CM a -> CM b -> CM a
|
||||
onChatError :: ChatMonad m => m a -> m b -> m a
|
||||
a `onChatError` onErr = a `catchChatError` \e -> onErr >> throwError e
|
||||
{-# INLINE onChatError #-}
|
||||
|
||||
@@ -1316,18 +1273,14 @@ mkStoreError = SEInternalError . show
|
||||
chatCmdError :: Maybe User -> String -> ChatResponse
|
||||
chatCmdError user = CRChatCmdError user . ChatError . CECommandError
|
||||
|
||||
throwChatError :: ChatErrorType -> CM a
|
||||
throwChatError :: ChatMonad m => ChatErrorType -> m a
|
||||
throwChatError = throwError . ChatError
|
||||
|
||||
-- | Emit local events.
|
||||
toView :: ChatResponse -> CM ()
|
||||
toView = lift . toView'
|
||||
{-# INLINE toView #-}
|
||||
|
||||
toView' :: ChatResponse -> CM' ()
|
||||
toView' ev = do
|
||||
cc@ChatController {outputQ = localQ, remoteCtrlSession = session, config = ChatConfig {chatHooks}} <- ask
|
||||
event <- liftIO $ eventHook chatHooks cc ev
|
||||
toView :: ChatMonad' m => ChatResponse -> m ()
|
||||
toView event = do
|
||||
localQ <- asks outputQ
|
||||
session <- asks remoteCtrlSession
|
||||
atomically $
|
||||
readTVar session >>= \case
|
||||
Just (_, RCSessionConnected {remoteOutputQ})
|
||||
@@ -1335,15 +1288,15 @@ toView' ev = do
|
||||
-- TODO potentially, it should hold some events while connecting
|
||||
_ -> writeTBQueue localQ (Nothing, Nothing, event)
|
||||
|
||||
withStore' :: (DB.Connection -> IO a) -> CM a
|
||||
withStore' :: ChatMonad m => (DB.Connection -> IO a) -> m a
|
||||
withStore' action = withStore $ liftIO . action
|
||||
|
||||
withStore :: (DB.Connection -> ExceptT StoreError IO a) -> CM a
|
||||
withStore :: ChatMonad m => (DB.Connection -> ExceptT StoreError IO a) -> m a
|
||||
withStore action = do
|
||||
ChatController {chatStore} <- ask
|
||||
liftIOEither $ 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 :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO (Either ChatError a))) -> m (t (Either ChatError a))
|
||||
withStoreBatch actions = do
|
||||
ChatController {chatStore} <- ask
|
||||
liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions
|
||||
@@ -1357,17 +1310,17 @@ handleDBErrors =
|
||||
E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e
|
||||
]
|
||||
|
||||
withStoreBatch' :: Traversable t => (DB.Connection -> t (IO a)) -> CM' (t (Either ChatError a))
|
||||
withStoreBatch' :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO a)) -> m (t (Either ChatError a))
|
||||
withStoreBatch' actions = withStoreBatch $ fmap (fmap Right) . actions
|
||||
|
||||
withAgent :: (AgentClient -> ExceptT AgentErrorType IO a) -> CM a
|
||||
withAgent :: ChatMonad m => (AgentClient -> ExceptT AgentErrorType m a) -> m a
|
||||
withAgent action =
|
||||
asks smpAgent
|
||||
>>= liftIO . runExceptT . action
|
||||
>>= runExceptT . action
|
||||
>>= liftEither . first (`ChatErrorAgent` Nothing)
|
||||
|
||||
withAgent' :: (AgentClient -> IO a) -> CM' a
|
||||
withAgent' action = asks smpAgent >>= liftIO . action
|
||||
withAgent' :: ChatMonad' m => (AgentClient -> m a) -> m a
|
||||
withAgent' action = asks smpAgent >>= action
|
||||
|
||||
$(JQ.deriveJSON (enumJSON $ dropPrefix "HS") ''HelpSection)
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
module Simplex.Chat.Files where
|
||||
|
||||
import Control.Monad.IO.Class
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Messaging.Util (ifM)
|
||||
import System.FilePath (combine, splitExtensions)
|
||||
import UnliftIO.Directory (doesDirectoryExist, doesFileExist, getHomeDirectory, getTemporaryDirectory)
|
||||
|
||||
uniqueCombine :: FilePath -> String -> IO FilePath
|
||||
uniqueCombine :: MonadIO m => FilePath -> String -> m FilePath
|
||||
uniqueCombine fPath fName = tryCombine (0 :: Int)
|
||||
where
|
||||
tryCombine n =
|
||||
@@ -17,10 +18,10 @@ uniqueCombine fPath fName = tryCombine (0 :: Int)
|
||||
f = fPath `combine` (name <> suffix <> ext)
|
||||
in ifM (doesFileExist f) (tryCombine $ n + 1) (pure f)
|
||||
|
||||
getChatTempDirectory :: CM' FilePath
|
||||
getChatTempDirectory = chatReadVar' tempDirectory >>= maybe getTemporaryDirectory pure
|
||||
getChatTempDirectory :: ChatMonad m => m FilePath
|
||||
getChatTempDirectory = chatReadVar tempDirectory >>= maybe getTemporaryDirectory pure
|
||||
|
||||
getDefaultFilesFolder :: CM' FilePath
|
||||
getDefaultFilesFolder :: ChatMonad m => m FilePath
|
||||
getDefaultFilesFolder = do
|
||||
dir <- (`combine` "Downloads") <$> getHomeDirectory
|
||||
ifM (doesDirectoryExist dir) (pure dir) getChatTempDirectory
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20240324_custom_data where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20240324_custom_data :: Query
|
||||
m20240324_custom_data =
|
||||
[sql|
|
||||
ALTER TABLE contacts ADD COLUMN custom_data BLOB;
|
||||
ALTER TABLE groups ADD COLUMN custom_data BLOB;
|
||||
|]
|
||||
|
||||
down_m20240324_custom_data :: Query
|
||||
down_m20240324_custom_data =
|
||||
[sql|
|
||||
ALTER TABLE contacts DROP COLUMN custom_data;
|
||||
ALTER TABLE groups DROP COLUMN custom_data;
|
||||
|]
|
||||
@@ -73,7 +73,6 @@ CREATE TABLE contacts(
|
||||
REFERENCES group_members(group_member_id) ON DELETE SET NULL,
|
||||
contact_grp_inv_sent INTEGER NOT NULL DEFAULT 0,
|
||||
contact_status TEXT NOT NULL DEFAULT 'active',
|
||||
custom_data BLOB,
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
@@ -121,8 +120,7 @@ CREATE TABLE groups(
|
||||
favorite INTEGER NOT NULL DEFAULT 0,
|
||||
send_rcpts INTEGER,
|
||||
via_group_link_uri_hash BLOB,
|
||||
user_member_profile_sent_at TEXT,
|
||||
custom_data BLOB, -- received
|
||||
user_member_profile_sent_at TEXT, -- received
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
|
||||
@@ -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
|
||||
@@ -47,13 +46,16 @@ import Simplex.Chat.Store.Profiles
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Messaging.Agent.Client (agentClientStore)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (createAgentStore)
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, closeSQLiteStore, reopenSQLiteStore)
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, connectSQLiteStore, 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 (..))
|
||||
import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8)
|
||||
import System.Directory
|
||||
import System.FilePath
|
||||
import System.IO (utf8)
|
||||
import System.Timeout (timeout)
|
||||
|
||||
@@ -233,7 +235,7 @@ chatMigrateInit dbFilePrefix dbKey confirm = chatMigrateInitKey dbFilePrefix dbK
|
||||
|
||||
chatMigrateInitKey :: String -> ScrubbedBytes -> Bool -> String -> Bool -> IO (Either DBMigrationResult ChatController)
|
||||
chatMigrateInitKey dbFilePrefix dbKey keepKey confirm backgroundMode = runExceptT $ do
|
||||
confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode $ B.pack confirm
|
||||
confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode @MigrationConfirmation $ B.pack confirm
|
||||
chatStore <- migrate createChatStore (chatStoreFile dbFilePrefix) confirmMigrations
|
||||
agentStore <- migrate createAgentStore (agentStoreFile dbFilePrefix) confirmMigrations
|
||||
liftIO $ initialize chatStore ChatDatabase {chatStore, agentStore}
|
||||
@@ -243,10 +245,15 @@ chatMigrateInitKey dbFilePrefix dbKey keepKey confirm backgroundMode = runExcept
|
||||
newChatController db user_ defaultMobileConfig (mobileChatOpts dbFilePrefix) backgroundMode
|
||||
migrate createStore dbFile confirmMigrations =
|
||||
ExceptT $
|
||||
(first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations)
|
||||
(first (DBMErrorMigration dbFile) <$> createStore' dbFile dbKey keepKey confirmMigrations)
|
||||
`catch` (pure . checkDBError)
|
||||
`catchAll` (pure . dbError)
|
||||
where
|
||||
createStore' dbFilePath dbKey keepKey _confirmMigrations = do
|
||||
let dbDir = takeDirectory dbFilePath
|
||||
createDirectoryIfMissing True dbDir
|
||||
Right <$> connectSQLiteStore dbFilePath dbKey keepKey
|
||||
|
||||
checkDBError e = case sqlError e of
|
||||
DB.ErrorNotADatabase -> Left $ DBMErrorNotADatabase dbFile
|
||||
_ -> dbError e
|
||||
|
||||
@@ -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
|
||||
|
||||
+80
-86
@@ -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 (($>))
|
||||
@@ -50,12 +49,13 @@ import Simplex.Chat.Store.Files
|
||||
import Simplex.Chat.Store.Remote
|
||||
import Simplex.Chat.Store.Shared
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Util (liftIOEither, encryptFile)
|
||||
import Simplex.Chat.Util (encryptFile)
|
||||
import Simplex.FileTransfer.Description (FileDigest (..))
|
||||
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)
|
||||
@@ -95,7 +95,7 @@ discoveryTimeout = 60000000
|
||||
|
||||
-- * Desktop side
|
||||
|
||||
getRemoteHostClient :: RemoteHostId -> CM RemoteHostClient
|
||||
getRemoteHostClient :: ChatMonad m => RemoteHostId -> m RemoteHostClient
|
||||
getRemoteHostClient rhId = do
|
||||
sessions <- asks remoteHostSessions
|
||||
liftIOEither . atomically $
|
||||
@@ -106,7 +106,7 @@ getRemoteHostClient rhId = do
|
||||
where
|
||||
rhKey = RHId rhId
|
||||
|
||||
withRemoteHostSession :: RHKey -> SessionSeq -> (RemoteHostSession -> Either ChatError (a, RemoteHostSession)) -> CM a
|
||||
withRemoteHostSession :: ChatMonad m => RHKey -> SessionSeq -> (RemoteHostSession -> Either ChatError (a, RemoteHostSession)) -> m a
|
||||
withRemoteHostSession rhKey sseq f = do
|
||||
sessions <- asks remoteHostSessions
|
||||
r <-
|
||||
@@ -121,7 +121,7 @@ withRemoteHostSession rhKey sseq f = do
|
||||
liftEither r
|
||||
|
||||
-- | Transition session state with a 'RHNew' ID to an assigned 'RemoteHostId'
|
||||
setNewRemoteHostId :: SessionSeq -> RemoteHostId -> CM ()
|
||||
setNewRemoteHostId :: ChatMonad m => SessionSeq -> RemoteHostId -> m ()
|
||||
setNewRemoteHostId sseq rhId = do
|
||||
sessions <- asks remoteHostSessions
|
||||
liftIOEither . atomically $ do
|
||||
@@ -136,13 +136,13 @@ setNewRemoteHostId sseq rhId = do
|
||||
where
|
||||
err = pure . Left . ChatErrorRemoteHost RHNew
|
||||
|
||||
startRemoteHost :: Maybe (RemoteHostId, Bool) -> Maybe RCCtrlAddress -> Maybe Word16 -> CM (NonEmpty RCCtrlAddress, Maybe RemoteHostInfo, RCSignedInvitation)
|
||||
startRemoteHost :: ChatMonad m => Maybe (RemoteHostId, Bool) -> Maybe RCCtrlAddress -> Maybe Word16 -> m (NonEmpty RCCtrlAddress, Maybe RemoteHostInfo, RCSignedInvitation)
|
||||
startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
(rhKey, multicast, remoteHost_, pairing) <- case rh_ of
|
||||
Just (rhId, multicast) -> do
|
||||
rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId
|
||||
pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested
|
||||
Nothing -> lift . withAgent' $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a
|
||||
Nothing -> withAgent $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a
|
||||
sseq <- startRemoteHostSession rhKey
|
||||
ctrlAppInfo <- mkCtrlAppInfo
|
||||
(localAddrs, invitation, rchClient, vars) <- handleConnectError rhKey sseq . withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast rcAddrPrefs_ port_
|
||||
@@ -170,18 +170,18 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
unless (isAppCompatible appVersion ctrlAppVersionRange) $ throwError $ RHEBadVersion appVersion
|
||||
when (encoding == PEKotlin && localEncoding == PESwift) $ throwError $ RHEProtocolError RPEIncompatibleEncoding
|
||||
pure hostInfo
|
||||
handleConnectError :: RHKey -> SessionSeq -> CM a -> CM a
|
||||
handleConnectError :: ChatMonad m => RHKey -> SessionSeq -> m a -> m a
|
||||
handleConnectError rhKey sessSeq action =
|
||||
action `catchChatError` \err -> do
|
||||
logError $ "startRemoteHost.rcConnectHost crashed: " <> tshow err
|
||||
cancelRemoteHostSession (Just (sessSeq, RHSRConnectionFailed err)) rhKey
|
||||
throwError err
|
||||
handleHostError :: SessionSeq -> TVar RHKey -> CM () -> CM ()
|
||||
handleHostError :: ChatMonad m => SessionSeq -> TVar RHKey -> m () -> m ()
|
||||
handleHostError sessSeq rhKeyVar action =
|
||||
action `catchChatError` \err -> do
|
||||
logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err
|
||||
readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just (sessSeq, RHSRCrashed err))
|
||||
waitForHostSession :: Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> CM ()
|
||||
waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m ()
|
||||
waitForHostSession remoteHost_ rhKey sseq rcAddr_ rhKeyVar vars = do
|
||||
(sessId, tls, vars') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars
|
||||
let sessionCode = verificationCode sessId
|
||||
@@ -203,7 +203,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
toView $ CRNewRemoteHost rhi
|
||||
-- set up HTTP transport and remote profile protocol
|
||||
disconnected <- toIO $ onDisconnected rhKey' sseq
|
||||
httpClient <- liftError' (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls
|
||||
httpClient <- liftEitherError (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls
|
||||
rhClient <- mkRemoteHostClient httpClient sessionKeys sessId storePath hostInfo
|
||||
pollAction <- async $ pollEvents remoteHostId rhClient
|
||||
withRemoteHostSession rhKey' sseq $ \case
|
||||
@@ -211,7 +211,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
|
||||
chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host
|
||||
toView $ CRRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}}
|
||||
upsertRemoteHost :: RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> CM RemoteHostInfo
|
||||
upsertRemoteHost :: ChatMonad m => RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> m RemoteHostInfo
|
||||
upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ rcAddr_ hostDeviceName sseq state = do
|
||||
KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_
|
||||
case rhi_ of
|
||||
@@ -223,11 +223,11 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
Just rhi@RemoteHostInfo {remoteHostId} -> do
|
||||
withStore' $ \db -> updateHostPairing db remoteHostId hostDeviceName hostDhPubKey' rcAddr_ port_
|
||||
pure (rhi :: RemoteHostInfo) {sessionState = Just state}
|
||||
onDisconnected :: RHKey -> SessionSeq -> CM ()
|
||||
onDisconnected :: ChatMonad m => RHKey -> SessionSeq -> m ()
|
||||
onDisconnected rhKey sseq = do
|
||||
logDebug $ "HTTP2 client disconnected: " <> tshow (rhKey, sseq)
|
||||
cancelRemoteHostSession (Just (sseq, RHSRDisconnected)) rhKey
|
||||
pollEvents :: RemoteHostId -> RemoteHostClient -> CM ()
|
||||
pollEvents :: ChatMonad m => RemoteHostId -> RemoteHostClient -> m ()
|
||||
pollEvents rhId rhClient = do
|
||||
oq <- asks outputQ
|
||||
forever $ do
|
||||
@@ -236,7 +236,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
||||
httpError :: RemoteHostId -> HTTP2ClientError -> ChatError
|
||||
httpError rhId = ChatErrorRemoteHost (RHId rhId) . RHEProtocolError . RPEHTTP2 . tshow
|
||||
|
||||
startRemoteHostSession :: RHKey -> CM SessionSeq
|
||||
startRemoteHostSession :: ChatMonad m => RHKey -> m SessionSeq
|
||||
startRemoteHostSession rhKey = do
|
||||
sessions <- asks remoteHostSessions
|
||||
nextSessionSeq <- asks remoteSessionSeq
|
||||
@@ -247,12 +247,12 @@ startRemoteHostSession rhKey = do
|
||||
sessionSeq <- stateTVar nextSessionSeq $ \s -> (s, s + 1)
|
||||
Right sessionSeq <$ TM.insert rhKey (sessionSeq, RHSessionStarting) sessions
|
||||
|
||||
closeRemoteHost :: RHKey -> CM ()
|
||||
closeRemoteHost :: ChatMonad m => RHKey -> m ()
|
||||
closeRemoteHost rhKey = do
|
||||
logNote $ "Closing remote host session for " <> tshow rhKey
|
||||
cancelRemoteHostSession Nothing rhKey
|
||||
|
||||
cancelRemoteHostSession :: Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> CM ()
|
||||
cancelRemoteHostSession :: ChatMonad m => Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> m ()
|
||||
cancelRemoteHostSession handlerInfo_ rhKey = do
|
||||
sessions <- asks remoteHostSessions
|
||||
crh <- asks currentRemoteHost
|
||||
@@ -299,7 +299,7 @@ cancelRemoteHost handlingError = \case
|
||||
randomStorePath :: IO FilePath
|
||||
randomStorePath = B.unpack . B64U.encode <$> getRandomBytes 12
|
||||
|
||||
listRemoteHosts :: CM [RemoteHostInfo]
|
||||
listRemoteHosts :: ChatMonad m => m [RemoteHostInfo]
|
||||
listRemoteHosts = do
|
||||
sessions <- chatReadVar remoteHostSessions
|
||||
map (rhInfo sessions) <$> withStore' getRemoteHosts
|
||||
@@ -307,7 +307,7 @@ listRemoteHosts = do
|
||||
rhInfo sessions rh@RemoteHost {remoteHostId} =
|
||||
remoteHostInfo rh $ rhsSessionState . snd <$> M.lookup (RHId remoteHostId) sessions
|
||||
|
||||
switchRemoteHost :: Maybe RemoteHostId -> CM (Maybe RemoteHostInfo)
|
||||
switchRemoteHost :: ChatMonad m => Maybe RemoteHostId -> m (Maybe RemoteHostInfo)
|
||||
switchRemoteHost rhId_ = do
|
||||
rhi_ <- forM rhId_ $ \rhId -> do
|
||||
let rhKey = RHId rhId
|
||||
@@ -322,7 +322,7 @@ remoteHostInfo :: RemoteHost -> Maybe RemoteHostSessionState -> RemoteHostInfo
|
||||
remoteHostInfo RemoteHost {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_} sessionState =
|
||||
RemoteHostInfo {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_, sessionState}
|
||||
|
||||
deleteRemoteHost :: RemoteHostId -> CM ()
|
||||
deleteRemoteHost :: ChatMonad m => RemoteHostId -> m ()
|
||||
deleteRemoteHost rhId = do
|
||||
RemoteHost {storePath} <- withStore (`getRemoteHost` rhId)
|
||||
chatReadVar remoteHostsFolder >>= \case
|
||||
@@ -333,7 +333,7 @@ deleteRemoteHost rhId = do
|
||||
Nothing -> logWarn "Local file store not available while deleting remote host"
|
||||
withStore' (`deleteRemoteHostRecord` rhId)
|
||||
|
||||
storeRemoteFile :: RemoteHostId -> Maybe Bool -> FilePath -> CM CryptoFile
|
||||
storeRemoteFile :: forall m. ChatMonad m => RemoteHostId -> Maybe Bool -> FilePath -> m CryptoFile
|
||||
storeRemoteFile rhId encrypted_ localPath = do
|
||||
c@RemoteHostClient {encryptHostFiles, storePath} <- getRemoteHostClient rhId
|
||||
let encrypt = fromMaybe encryptHostFiles encrypted_
|
||||
@@ -347,23 +347,23 @@ storeRemoteFile rhId encrypted_ localPath = do
|
||||
(if encrypt then renameFile else copyFile) filePath hPath
|
||||
pure (cf :: CryptoFile) {filePath = filePath'}
|
||||
where
|
||||
encryptLocalFile :: CM CryptoFile
|
||||
encryptLocalFile :: m CryptoFile
|
||||
encryptLocalFile = do
|
||||
tmpDir <- lift getChatTempDirectory
|
||||
tmpDir <- getChatTempDirectory
|
||||
createDirectoryIfMissing True tmpDir
|
||||
tmpFile <- liftIO $ tmpDir `uniqueCombine` takeFileName localPath
|
||||
tmpFile <- tmpDir `uniqueCombine` takeFileName localPath
|
||||
cfArgs <- atomically . CF.randomArgs =<< asks random
|
||||
liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs
|
||||
pure $ CryptoFile tmpFile $ Just cfArgs
|
||||
|
||||
getRemoteFile :: RemoteHostId -> RemoteFile -> CM ()
|
||||
getRemoteFile :: ChatMonad m => RemoteHostId -> RemoteFile -> m ()
|
||||
getRemoteFile rhId rf = do
|
||||
c@RemoteHostClient {storePath} <- getRemoteHostClient rhId
|
||||
dir <- lift $ (</> storePath </> archiveFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar' remoteHostsFolder)
|
||||
dir <- (</> storePath </> archiveFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar remoteHostsFolder)
|
||||
createDirectoryIfMissing True dir
|
||||
liftRH rhId $ remoteGetFile c dir rf
|
||||
|
||||
processRemoteCommand :: RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteString -> CM ChatResponse
|
||||
processRemoteCommand :: ChatMonad m => RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteString -> m ChatResponse
|
||||
processRemoteCommand remoteHostId c cmd s = case cmd of
|
||||
SendFile chatName f -> sendFile "/f" chatName f
|
||||
SendImage chatName f -> sendFile "/img" chatName f
|
||||
@@ -378,7 +378,7 @@ processRemoteCommand remoteHostId c cmd s = case cmd of
|
||||
maybe "" (\(CFArgs key nonce) -> "key=" <> strEncode key <> " nonce=" <> strEncode nonce <> " ") cryptoArgs
|
||||
<> encodeUtf8 (T.pack filePath)
|
||||
|
||||
liftRH :: RemoteHostId -> ExceptT RemoteProtocolError IO a -> CM a
|
||||
liftRH :: ChatMonad m => RemoteHostId -> ExceptT RemoteProtocolError IO a -> m a
|
||||
liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError)
|
||||
|
||||
-- * Mobile side
|
||||
@@ -386,7 +386,7 @@ liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError)
|
||||
-- ** QR/link
|
||||
|
||||
-- | Use provided OOB link as an annouce
|
||||
connectRemoteCtrlURI :: RCSignedInvitation -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
||||
connectRemoteCtrlURI :: ChatMonad m => RCSignedInvitation -> m (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
||||
connectRemoteCtrlURI signedInv = do
|
||||
verifiedInv <- maybe (throwError $ ChatErrorRemoteCtrl RCEBadInvitation) pure $ verifySignedInvitation signedInv
|
||||
sseq <- startRemoteCtrlSession
|
||||
@@ -394,7 +394,7 @@ connectRemoteCtrlURI signedInv = do
|
||||
|
||||
-- ** Multicast
|
||||
|
||||
findKnownRemoteCtrl :: CM ()
|
||||
findKnownRemoteCtrl :: ChatMonad m => m ()
|
||||
findKnownRemoteCtrl = do
|
||||
knownCtrls <- withStore' getRemoteCtrls
|
||||
pairings <- case nonEmpty knownCtrls of
|
||||
@@ -420,7 +420,7 @@ findKnownRemoteCtrl = do
|
||||
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
||||
atomically $ putTMVar cmdOk ()
|
||||
|
||||
confirmRemoteCtrl :: RemoteCtrlId -> CM (RemoteCtrlInfo, CtrlAppInfo)
|
||||
confirmRemoteCtrl :: ChatMonad m => RemoteCtrlId -> m (RemoteCtrlInfo, CtrlAppInfo)
|
||||
confirmRemoteCtrl rcId = do
|
||||
session <- asks remoteCtrlSession
|
||||
(sseq, listener, found) <- liftIOEither $ atomically $ do
|
||||
@@ -438,7 +438,7 @@ confirmRemoteCtrl rcId = do
|
||||
|
||||
-- ** Common
|
||||
|
||||
startRemoteCtrlSession :: CM SessionSeq
|
||||
startRemoteCtrlSession :: ChatMonad m => m SessionSeq
|
||||
startRemoteCtrlSession = do
|
||||
session <- asks remoteCtrlSession
|
||||
nextSessionSeq <- asks remoteSessionSeq
|
||||
@@ -449,7 +449,7 @@ startRemoteCtrlSession = do
|
||||
sseq <- stateTVar nextSessionSeq $ \s -> (s, s + 1)
|
||||
Right sseq <$ writeTVar session (Just (sseq, RCSessionStarting))
|
||||
|
||||
connectRemoteCtrl :: RCVerifiedInvitation -> SessionSeq -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
||||
connectRemoteCtrl :: ChatMonad m => RCVerifiedInvitation -> SessionSeq -> m (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
||||
connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq RCSRConnectionFailed "connectRemoteCtrl" $ do
|
||||
ctrlInfo@CtrlAppInfo {deviceName = ctrlDeviceName} <- parseCtrlAppInfo app
|
||||
v <- checkAppVersion ctrlInfo
|
||||
@@ -470,7 +470,7 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app})
|
||||
where
|
||||
validateRemoteCtrl RCInvitation {idkey} RemoteCtrl {ctrlPairing = RCCtrlPairing {idPubKey}} =
|
||||
unless (idkey == idPubKey) $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError $ PRERemoteControl RCEIdentity
|
||||
waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM ()
|
||||
waitForCtrlSession :: ChatMonad m => Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> m ()
|
||||
waitForCtrlSession rc_ ctrlName rcsClient vars = do
|
||||
(uniq, tls, rcsWaitConfirmation) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars
|
||||
let sessionCode = verificationCode uniq
|
||||
@@ -489,18 +489,18 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app})
|
||||
encryptFiles <- chatReadVar encryptLocalFiles
|
||||
pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles}
|
||||
|
||||
parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo
|
||||
parseCtrlAppInfo :: ChatMonad m => JT.Value -> m CtrlAppInfo
|
||||
parseCtrlAppInfo ctrlAppInfo = do
|
||||
liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo
|
||||
|
||||
handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> CM' ()
|
||||
handleRemoteCommand :: forall m. ChatMonad m => (ByteString -> m ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> m ()
|
||||
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)
|
||||
@@ -508,72 +508,67 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque
|
||||
(header, getNext) <- parseDecryptHTTP2Body encryption request reqBody
|
||||
(getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header)
|
||||
replyError = reply . RRChatResponse . CRChatCmdError Nothing
|
||||
processCommand :: User -> GetChunk -> RemoteCommand -> CM ()
|
||||
processCommand :: User -> GetChunk -> RemoteCommand -> m ()
|
||||
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} -> handleSend execChatCommand command >>= reply
|
||||
RCRecv {wait = time} -> handleRecv time remoteOutputQ >>= reply
|
||||
RCStoreFile {fileName, fileSize, fileDigest} -> handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply
|
||||
RCGetFile {file} -> handleGetFile encryption user file replyWith
|
||||
reply :: RemoteResponse -> CM' ()
|
||||
reply :: RemoteResponse -> m ()
|
||||
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 :: Respond m
|
||||
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
|
||||
takeRCStep :: ChatMonad m => RCStepTMVar a -> m a
|
||||
takeRCStep = liftEitherError (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar
|
||||
|
||||
type GetChunk = Int -> IO ByteString
|
||||
|
||||
type SendChunk = Builder -> IO ()
|
||||
|
||||
type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM' ()
|
||||
type Respond m = RemoteResponse -> (SendChunk -> IO ()) -> m ()
|
||||
|
||||
liftRC :: ExceptT RemoteProtocolError IO a -> CM a
|
||||
liftRC :: ChatMonad m => ExceptT RemoteProtocolError IO a -> m a
|
||||
liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError)
|
||||
|
||||
tryRemoteError :: ExceptT RemoteProtocolError IO a -> ExceptT RemoteProtocolError IO (Either RemoteProtocolError a)
|
||||
tryRemoteError = tryAllErrors (RPEException . tshow)
|
||||
{-# INLINE tryRemoteError #-}
|
||||
|
||||
tryRemoteError' :: ExceptT RemoteProtocolError IO a -> IO (Either RemoteProtocolError a)
|
||||
tryRemoteError' = tryAllErrors' (RPEException . tshow)
|
||||
{-# INLINE tryRemoteError' #-}
|
||||
|
||||
handleSend :: (ByteString -> CM' ChatResponse) -> Text -> CM' RemoteResponse
|
||||
handleSend :: ChatMonad m => (ByteString -> m ChatResponse) -> Text -> m RemoteResponse
|
||||
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)
|
||||
-- convert errors thrown in ChatMonad into error responses to prevent aborting the protocol wrapper
|
||||
RRChatResponse <$> execChatCommand (encodeUtf8 command) `catchError` (pure . CRChatError Nothing)
|
||||
|
||||
handleRecv :: Int -> TBQueue ChatResponse -> IO RemoteResponse
|
||||
handleRecv :: MonadUnliftIO m => Int -> TBQueue ChatResponse -> m RemoteResponse
|
||||
handleRecv time events = do
|
||||
logDebug $ "Recv: " <> tshow time
|
||||
RRChatEvent <$> (timeout time . atomically $ readTBQueue events)
|
||||
|
||||
-- TODO this command could remember stored files and return IDs to allow removing files that are not needed.
|
||||
-- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files).
|
||||
handleStoreFile :: RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse
|
||||
handleStoreFile :: forall m. ChatMonad m => RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> m RemoteResponse
|
||||
handleStoreFile encryption fileName fileSize fileDigest getChunk =
|
||||
either RRProtocolError RRFileStored <$> (chatReadVar' filesFolder >>= storeFile)
|
||||
either RRProtocolError RRFileStored <$> (chatReadVar filesFolder >>= storeFile)
|
||||
where
|
||||
storeFile :: Maybe FilePath -> CM' (Either RemoteProtocolError FilePath)
|
||||
storeFile :: Maybe FilePath -> m (Either RemoteProtocolError FilePath)
|
||||
storeFile = \case
|
||||
Just ff -> takeFileName <$$> storeFileTo ff
|
||||
Nothing -> storeFileTo =<< getDefaultFilesFolder
|
||||
storeFileTo :: FilePath -> CM' (Either RemoteProtocolError FilePath)
|
||||
storeFileTo dir = liftIO . tryRemoteError' $ do
|
||||
filePath <- liftIO $ dir `uniqueCombine` fileName
|
||||
storeFileTo :: FilePath -> m (Either RemoteProtocolError FilePath)
|
||||
storeFileTo dir = liftRC . tryRemoteError $ do
|
||||
filePath <- dir `uniqueCombine` fileName
|
||||
receiveEncryptedFile encryption getChunk fileSize fileDigest filePath
|
||||
pure filePath
|
||||
|
||||
handleGetFile :: RemoteCrypto -> User -> RemoteFile -> Respond -> CM ()
|
||||
handleGetFile :: ChatMonad m => RemoteCrypto -> User -> RemoteFile -> Respond m -> m ()
|
||||
handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do
|
||||
logDebug $ "GetFile: " <> tshow filePath
|
||||
unless (userId == commandUserId) $ throwChatError $ CEDifferentActiveUser {commandUserId, activeUserId = userId}
|
||||
@@ -582,13 +577,13 @@ 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 :: ChatMonad m => m [RemoteCtrlInfo]
|
||||
listRemoteCtrls = do
|
||||
session <- snd <$$> chatReadVar remoteCtrlSession
|
||||
let rcId = sessionRcId =<< session
|
||||
@@ -609,7 +604,7 @@ remoteCtrlInfo RemoteCtrl {remoteCtrlId, ctrlDeviceName} sessionState =
|
||||
RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionState}
|
||||
|
||||
-- | Take a look at emoji of tlsunique, commit pairing, and start session server
|
||||
verifyRemoteCtrlSession :: (ByteString -> CM' ChatResponse) -> Text -> CM RemoteCtrlInfo
|
||||
verifyRemoteCtrlSession :: ChatMonad m => (ByteString -> m ChatResponse) -> Text -> m RemoteCtrlInfo
|
||||
verifyRemoteCtrlSession execChatCommand sessCode' = do
|
||||
(sseq, client, ctrlName, sessionCode, vars) <-
|
||||
chatReadVar remoteCtrlSession >>= \case
|
||||
@@ -624,15 +619,14 @@ 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}
|
||||
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
||||
pure $ remoteCtrlInfo rc $ Just RCSConnected {sessionCode = tlsSessionCode tls}
|
||||
where
|
||||
upsertRemoteCtrl :: Text -> RCCtrlPairing -> CM RemoteCtrl
|
||||
upsertRemoteCtrl :: ChatMonad m => Text -> RCCtrlPairing -> m RemoteCtrl
|
||||
upsertRemoteCtrl ctrlName rcCtrlPairing = withStore $ \db -> do
|
||||
rc_ <- liftIO $ getRemoteCtrlByFingerprint db (ctrlFingerprint rcCtrlPairing)
|
||||
case rc_ of
|
||||
@@ -641,16 +635,16 @@ verifyRemoteCtrlSession execChatCommand sessCode' = do
|
||||
let dhPrivKey' = dhPrivKey rcCtrlPairing
|
||||
liftIO $ updateRemoteCtrl db rc ctrlName dhPrivKey'
|
||||
pure rc {ctrlDeviceName = ctrlName, ctrlPairing = ctrlPairing {dhPrivKey = dhPrivKey'}}
|
||||
monitor :: SessionSeq -> Async () -> CM ()
|
||||
monitor :: ChatMonad m => SessionSeq -> Async () -> m ()
|
||||
monitor sseq server = do
|
||||
res <- waitCatch server
|
||||
logInfo $ "HTTP2 server stopped: " <> tshow res
|
||||
cancelActiveRemoteCtrl $ Just (sseq, RCSRDisconnected)
|
||||
|
||||
stopRemoteCtrl :: CM ()
|
||||
stopRemoteCtrl :: ChatMonad m => m ()
|
||||
stopRemoteCtrl = cancelActiveRemoteCtrl Nothing
|
||||
|
||||
handleCtrlError :: SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> CM a -> CM a
|
||||
handleCtrlError :: ChatMonad m => SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> m a -> m a
|
||||
handleCtrlError sseq mkReason name action =
|
||||
action `catchChatError` \e -> do
|
||||
logError $ name <> " remote ctrl error: " <> tshow e
|
||||
@@ -658,7 +652,7 @@ handleCtrlError sseq mkReason name action =
|
||||
throwError e
|
||||
|
||||
-- | Stop session controller, unless session update key is present but stale
|
||||
cancelActiveRemoteCtrl :: Maybe (SessionSeq, RemoteCtrlStopReason) -> CM ()
|
||||
cancelActiveRemoteCtrl :: ChatMonad m => Maybe (SessionSeq, RemoteCtrlStopReason) -> m ()
|
||||
cancelActiveRemoteCtrl handlerInfo_ = handleAny (logError . tshow) $ do
|
||||
var <- asks remoteCtrlSession
|
||||
session_ <-
|
||||
@@ -691,18 +685,18 @@ cancelRemoteCtrl handlingError = \case
|
||||
cancelCtrlClient rcsClient
|
||||
closeConnection tls
|
||||
|
||||
deleteRemoteCtrl :: RemoteCtrlId -> CM ()
|
||||
deleteRemoteCtrl :: ChatMonad m => RemoteCtrlId -> m ()
|
||||
deleteRemoteCtrl rcId = do
|
||||
checkNoRemoteCtrlSession
|
||||
-- TODO check it exists
|
||||
withStore' (`deleteRemoteCtrlRecord` rcId)
|
||||
|
||||
checkNoRemoteCtrlSession :: CM ()
|
||||
checkNoRemoteCtrlSession :: ChatMonad m => m ()
|
||||
checkNoRemoteCtrlSession =
|
||||
chatReadVar remoteCtrlSession >>= maybe (pure ()) (\_ -> throwError $ ChatErrorRemoteCtrl RCEBusy)
|
||||
|
||||
-- | Transition controller to a new state, unless session update key is stale
|
||||
updateRemoteCtrlSession :: SessionSeq -> (RemoteCtrlSession -> Either ChatError RemoteCtrlSession) -> CM ()
|
||||
updateRemoteCtrlSession :: ChatMonad m => SessionSeq -> (RemoteCtrlSession -> Either ChatError RemoteCtrlSession) -> m ()
|
||||
updateRemoteCtrlSession sseq state = do
|
||||
session <- asks remoteCtrlSession
|
||||
r <- atomically $ do
|
||||
|
||||
@@ -46,7 +46,7 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFi
|
||||
import Simplex.Messaging.Transport.Buffer (getBuffered)
|
||||
import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..), HTTP2BodyChunk, getBodyChunk)
|
||||
import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2Response (..), closeHTTP2Client, sendRequestDirect)
|
||||
import Simplex.Messaging.Util (liftError', liftEitherWith, liftError, tshow)
|
||||
import Simplex.Messaging.Util (liftEitherError, liftEitherWith, liftError, tshow)
|
||||
import Simplex.RemoteControl.Client (xrcpBlockSize)
|
||||
import qualified Simplex.RemoteControl.Client as RC
|
||||
import Simplex.RemoteControl.Types (CtrlSessKeys (..), HostSessKeys (..), RCErrorType (..), SessionCode)
|
||||
@@ -75,7 +75,7 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse)
|
||||
|
||||
-- * Client side / desktop
|
||||
|
||||
mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> CM RemoteHostClient
|
||||
mkRemoteHostClient :: ChatMonad m => HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> m RemoteHostClient
|
||||
mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do
|
||||
drg <- asks random
|
||||
counter <- newTVarIO 1
|
||||
@@ -92,15 +92,15 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc
|
||||
storePath
|
||||
}
|
||||
|
||||
mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> CM RemoteCrypto
|
||||
mkCtrlRemoteCrypto :: ChatMonad m => CtrlSessKeys -> SessionCode -> m RemoteCrypto
|
||||
mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do
|
||||
drg <- asks random
|
||||
counter <- newTVarIO 1
|
||||
let signatures = RSVerify {idPubKey, sessPubKey}
|
||||
pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures}
|
||||
|
||||
closeRemoteHostClient :: RemoteHostClient -> IO ()
|
||||
closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClient
|
||||
closeRemoteHostClient :: MonadIO m => RemoteHostClient -> m ()
|
||||
closeRemoteHostClient RemoteHostClient {httpClient} = liftIO $ closeHTTP2Client httpClient
|
||||
|
||||
-- ** Commands
|
||||
|
||||
@@ -141,7 +141,7 @@ sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand
|
||||
sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} file_ cmd = do
|
||||
encFile_ <- mapM (prepareEncryptedFile encryption) file_
|
||||
req <- httpRequest encFile_ <$> encryptEncodeHTTP2Body encryption (J.encode cmd)
|
||||
HTTP2Response {response, respBody} <- liftError' (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing
|
||||
HTTP2Response {response, respBody} <- liftEitherError (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing
|
||||
(header, getNext) <- parseDecryptHTTP2Body encryption response respBody
|
||||
rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding
|
||||
pure (getNext, rr)
|
||||
@@ -271,7 +271,7 @@ parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2
|
||||
where
|
||||
getSig = do
|
||||
len <- liftIO $ B.head <$> getNext 1
|
||||
liftError' RPEInvalidBody $ C.decodeSignature <$> getNext (fromIntegral len)
|
||||
liftEitherError RPEInvalidBody $ C.decodeSignature <$> getNext (fromIntegral len)
|
||||
verifySig key sig hc' = do
|
||||
let signed = BA.convert $ CH.hashFinalize hc'
|
||||
unless (C.verify' key sig signed) $ throwError $ PRERemoteControl RCECtrlAuth
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@ import Simplex.FileTransfer.Transport (ReceiveFileError (..), receiveSbFile, sen
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Crypto.Lazy as LC
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Util (liftError', liftEitherWith)
|
||||
import Simplex.Messaging.Util (liftEitherError, liftEitherWith)
|
||||
import Simplex.RemoteControl.Types (RCErrorType (..))
|
||||
import UnliftIO
|
||||
import UnliftIO.Directory (getFileSize)
|
||||
@@ -37,11 +37,11 @@ receiveEncryptedFile :: RemoteCrypto -> (Int -> IO ByteString) -> Word32 -> File
|
||||
receiveEncryptedFile RemoteCrypto {hybridKey} getChunk fileSize fileDigest toPath = do
|
||||
c <- liftIO $ getChunk 1
|
||||
unless (c == "\x01") $ throwError RPENoFile
|
||||
nonce <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 24
|
||||
size <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 4
|
||||
nonce <- liftEitherError RPEInvalidBody $ smpDecode <$> getChunk 24
|
||||
size <- liftEitherError RPEInvalidBody $ smpDecode <$> getChunk 4
|
||||
unless (size == fileSize + fromIntegral C.authTagSize) $ throwError RPEFileSize
|
||||
sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.kcbInit hybridKey nonce
|
||||
liftError' fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize
|
||||
liftEitherError fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize
|
||||
digest <- liftIO $ LC.sha512Hash <$> LB.readFile toPath
|
||||
unless (FileDigest digest == fileDigest) $ throwError RPEFileDigest
|
||||
where
|
||||
|
||||
@@ -75,19 +75,19 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
[sql|
|
||||
SELECT
|
||||
c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.via_group, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite,
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent, c.custom_data
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent
|
||||
FROM contacts c
|
||||
JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id
|
||||
WHERE c.user_id = ? AND c.contact_id = ? AND c.deleted = 0
|
||||
|]
|
||||
(userId, contactId)
|
||||
toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool, Maybe CustomData)] -> Either StoreError Contact
|
||||
toContact' contactId conn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData)] =
|
||||
toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool)] -> Either StoreError Contact
|
||||
toContact' contactId conn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)] =
|
||||
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
activeConn = Just conn
|
||||
in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData}
|
||||
in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent}
|
||||
toContact' _ _ _ = Left $ SEInternalError "referenced contact not found"
|
||||
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
getGroupAndMember_ groupMemberId c = ExceptT $ do
|
||||
@@ -99,7 +99,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
|
||||
@@ -68,7 +68,6 @@ module Simplex.Chat.Store.Direct
|
||||
updateContactSettings,
|
||||
setConnConnReqInv,
|
||||
resetContactConnInitiated,
|
||||
setContactCustomData,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -176,7 +175,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash =
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter,
|
||||
@@ -222,7 +221,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p
|
||||
let profile = toLocalProfile profileId p localAlias
|
||||
userPreferences = emptyChatPrefs
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
pure $ Contact {contactId, localDisplayName, profile, activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False, customData = Nothing}
|
||||
pure $ Contact {contactId, localDisplayName, profile, activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False}
|
||||
|
||||
deleteContactConnectionsAndFiles :: DB.Connection -> UserId -> Contact -> IO ()
|
||||
deleteContactConnectionsAndFiles db userId Contact {contactId} = do
|
||||
@@ -579,7 +578,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter,
|
||||
@@ -725,7 +724,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
|
||||
contactId <- insertedRowId db
|
||||
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
|
||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt, contactGroupMemberId = Nothing, contactGrpInvSent = False, customData = Nothing}
|
||||
pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn = Just conn, viaGroup = Nothing, contactUsed, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt, contactGroupMemberId = Nothing, contactGrpInvSent = False}
|
||||
|
||||
getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64
|
||||
getContactIdByName db User {userId} cName =
|
||||
@@ -744,7 +743,7 @@ getContact_ db vr user@User {userId} contactId deleted =
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.custom_data,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter,
|
||||
@@ -884,8 +883,3 @@ resetContactConnInitiated db User {userId} Connection {connId} = do
|
||||
WHERE user_id = ? AND connection_id = ?
|
||||
|]
|
||||
(updatedAt, userId, connId)
|
||||
|
||||
setContactCustomData :: DB.Connection -> User -> Contact -> Maybe CustomData -> IO ()
|
||||
setContactCustomData db User {userId} Contact {contactId} customData = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE contacts SET custom_data = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (customData, updatedAt, userId, contactId)
|
||||
|
||||
@@ -116,7 +116,6 @@ module Simplex.Chat.Store.Groups
|
||||
createNewUnknownGroupMember,
|
||||
updateUnknownMemberAnnounced,
|
||||
updateUserMemberProfileSentAt,
|
||||
setGroupCustomData,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -149,19 +148,19 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>))
|
||||
import Simplex.Messaging.Version
|
||||
import UnliftIO.STM
|
||||
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe CustomData) :. GroupMemberRow
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. GroupMemberRow
|
||||
|
||||
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
|
||||
|
||||
type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
|
||||
|
||||
toGroupInfo :: (PQSupport -> VersionRangeChat) -> Int64 -> GroupInfoRow -> GroupInfo
|
||||
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, customData) :. userMemberRow) =
|
||||
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. userMemberRow) =
|
||||
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr PQSupportOff}
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
||||
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
||||
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
|
||||
in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, customData}
|
||||
in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt}
|
||||
|
||||
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
|
||||
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
|
||||
@@ -272,7 +271,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
@@ -345,8 +344,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
userMemberProfileSentAt = Just currentTs,
|
||||
customData = Nothing
|
||||
userMemberProfileSentAt = Just currentTs
|
||||
}
|
||||
|
||||
-- | creates a new group record for the group the current user was invited to, or returns an existing one
|
||||
@@ -411,8 +409,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
userMemberProfileSentAt = Just currentTs,
|
||||
customData = Nothing
|
||||
userMemberProfileSentAt = Just currentTs
|
||||
},
|
||||
groupMemberId
|
||||
)
|
||||
@@ -631,7 +628,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ =
|
||||
SELECT
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
|
||||
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
|
||||
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
|
||||
FROM groups g
|
||||
@@ -1296,7 +1293,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
@@ -1392,7 +1389,7 @@ getGroupInfo db vr User {userId, userContactId} groupId =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
|
||||
-- GroupMember - membership
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
@@ -1954,7 +1951,7 @@ createMemberContact
|
||||
authErrCounter = 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, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = 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, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False}
|
||||
|
||||
getMemberContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
|
||||
getMemberContact db vr user contactId = do
|
||||
@@ -1991,7 +1988,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, contactGroupMemberId = Nothing, contactGrpInvSent = 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, contactGroupMemberId = Nothing, contactGrpInvSent = False}
|
||||
m' = m {memberContactId = Just contactId}
|
||||
pure (mCt', m')
|
||||
where
|
||||
@@ -2191,8 +2188,3 @@ updateUserMemberProfileSentAt db User {userId} GroupInfo {groupId} sentTs =
|
||||
db
|
||||
"UPDATE groups SET user_member_profile_sent_at = ? WHERE user_id = ? AND group_id = ?"
|
||||
(sentTs, userId, groupId)
|
||||
|
||||
setGroupCustomData :: DB.Connection -> User -> GroupInfo -> Maybe CustomData -> IO ()
|
||||
setGroupCustomData db User {userId} GroupInfo {groupId} customData = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute db "UPDATE groups SET custom_data = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (customData, updatedAt, userId, groupId)
|
||||
|
||||
@@ -41,7 +41,6 @@ module Simplex.Chat.Store.Messages
|
||||
getDirectChatItemLast,
|
||||
getAllChatItems,
|
||||
getAChatItem,
|
||||
getAChatItemBySharedMsgId,
|
||||
updateDirectChatItem,
|
||||
updateDirectChatItem',
|
||||
addInitialAndNewCIVersions,
|
||||
@@ -2203,15 +2202,6 @@ getAChatItem db vr user chatRef itemId = case chatRef of
|
||||
pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci
|
||||
_ -> throwError $ SEChatItemNotFound itemId
|
||||
|
||||
getAChatItemBySharedMsgId :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> SharedMsgId -> ExceptT StoreError IO AChatItem
|
||||
getAChatItemBySharedMsgId db user cd sharedMsgId = case cd of
|
||||
CDDirectRcv ct@Contact {contactId} -> do
|
||||
(CChatItem msgDir ci) <- getDirectChatItemBySharedMsgId db user contactId sharedMsgId
|
||||
pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci
|
||||
CDGroupRcv g@GroupInfo {groupId} GroupMember {groupMemberId} -> do
|
||||
(CChatItem msgDir ci) <- getGroupChatItemBySharedMsgId db user groupId groupMemberId sharedMsgId
|
||||
pure $ AChatItem SCTGroup msgDir (GroupChat g) ci
|
||||
|
||||
getChatItemVersions :: DB.Connection -> ChatItemId -> IO [ChatItemVersion]
|
||||
getChatItemVersions db itemId = do
|
||||
map toChatItemVersion
|
||||
|
||||
@@ -103,7 +103,6 @@ import Simplex.Chat.Migrations.M20240222_app_settings
|
||||
import Simplex.Chat.Migrations.M20240226_users_restrict
|
||||
import Simplex.Chat.Migrations.M20240228_pq
|
||||
import Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id
|
||||
import Simplex.Chat.Migrations.M20240324_custom_data
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -206,8 +205,7 @@ schemaMigrations =
|
||||
("20240222_app_settings", m20240222_app_settings, Just down_m20240222_app_settings),
|
||||
("20240226_users_restrict", m20240226_users_restrict, Just down_m20240226_users_restrict),
|
||||
("20240228_pq", m20240228_pq, Just down_m20240228_pq),
|
||||
("20240313_drop_agent_ack_cmd_id", m20240313_drop_agent_ack_cmd_id, Just down_m20240313_drop_agent_ack_cmd_id),
|
||||
("20240324_custom_data", m20240324_custom_data, Just down_m20240324_custom_data)
|
||||
("20240313_drop_agent_ack_cmd_id", m20240313_drop_agent_ack_cmd_id, Just down_m20240313_drop_agent_ack_cmd_id)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -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)
|
||||
@@ -371,16 +371,16 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId =
|
||||
|]
|
||||
[":user_id" := userId, ":profile_id" := profileId]
|
||||
|
||||
type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool, Maybe CustomData)
|
||||
type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool)
|
||||
|
||||
toContact :: (PQSupport -> VersionRangeChat) -> User -> ContactRow :. MaybeConnectionRow -> Contact
|
||||
toContact vr user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData)) :. connRow) =
|
||||
toContact vr user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)) :. connRow) =
|
||||
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||
activeConn = toMaybeConnection vr connRow
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
||||
incognito = maybe False connIncognito activeConn
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, customData}
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent}
|
||||
|
||||
getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile
|
||||
getProfileById db userId profileId =
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
||||
module Simplex.Chat.Terminal.Main where
|
||||
|
||||
import Control.Concurrent (forkIO, threadDelay)
|
||||
import Control.Concurrent.STM
|
||||
import Control.Monad
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
import Data.Time.LocalTime (getCurrentTimeZone)
|
||||
import Network.Socket
|
||||
import Simplex.Chat.Controller (ChatConfig, ChatController (..), ChatResponse (..), currentRemoteHost, versionNumber, versionString)
|
||||
import Simplex.Chat.Core
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Terminal
|
||||
import Simplex.Chat.View (serializeChatResponse)
|
||||
import Simplex.Messaging.Client (NetworkConfig (..))
|
||||
import System.Directory (getAppUserDataDirectory)
|
||||
import System.Exit (exitFailure)
|
||||
import System.Terminal (withTerminal)
|
||||
|
||||
simplexChatCLI :: ChatConfig -> Maybe (ServiceName -> ChatConfig -> ChatOpts -> IO ()) -> IO ()
|
||||
simplexChatCLI cfg server_ = do
|
||||
appDir <- getAppUserDataDirectory "simplex"
|
||||
opts@ChatOpts {chatCmd, chatServerPort} <- getChatOpts appDir "simplex_v1"
|
||||
if null chatCmd
|
||||
then case chatServerPort of
|
||||
Just chatPort -> case server_ of
|
||||
Just server -> server chatPort cfg opts
|
||||
Nothing -> putStrLn "Not allowed to run as a WebSockets server" >> exitFailure
|
||||
_ -> runCLI opts
|
||||
else simplexChatCore cfg opts $ runCommand opts
|
||||
where
|
||||
runCLI opts = do
|
||||
welcome opts
|
||||
t <- withTerminal pure
|
||||
simplexChatTerminal cfg opts t
|
||||
runCommand ChatOpts {chatCmd, chatCmdLog, chatCmdDelay} user cc = do
|
||||
when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do
|
||||
(_, _, r') <- atomically . readTBQueue $ outputQ cc
|
||||
case r' of
|
||||
CRNewChatItem {} -> printResponse r'
|
||||
_ -> when (chatCmdLog == CCLAll) $ printResponse r'
|
||||
sendChatCmdStr cc chatCmd >>= printResponse
|
||||
threadDelay $ chatCmdDelay * 1000000
|
||||
where
|
||||
printResponse r = do
|
||||
ts <- getCurrentTime
|
||||
tz <- getCurrentTimeZone
|
||||
rh <- readTVarIO $ currentRemoteHost cc
|
||||
putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r
|
||||
|
||||
welcome :: ChatOpts -> IO ()
|
||||
welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} =
|
||||
mapM_
|
||||
putStrLn
|
||||
[ versionString versionNumber,
|
||||
"db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db",
|
||||
maybe
|
||||
"direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050"
|
||||
(("using SOCKS5 proxy " <>) . show)
|
||||
(socksProxy networkConfig),
|
||||
"type \"/help\" or \"/h\" for usage info"
|
||||
]
|
||||
@@ -174,25 +174,10 @@ data Contact = Contact
|
||||
updatedAt :: UTCTime,
|
||||
chatTs :: Maybe UTCTime,
|
||||
contactGroupMemberId :: Maybe GroupMemberId,
|
||||
contactGrpInvSent :: Bool,
|
||||
customData :: Maybe CustomData
|
||||
contactGrpInvSent :: Bool
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
newtype CustomData = CustomData J.Object
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance ToJSON CustomData where
|
||||
toJSON (CustomData v) = toJSON v
|
||||
toEncoding (CustomData v) = toEncoding v
|
||||
|
||||
instance FromJSON CustomData where
|
||||
parseJSON = J.withObject "CustomData" (pure . CustomData)
|
||||
|
||||
instance ToField CustomData where toField (CustomData v) = toField $ J.encode v
|
||||
|
||||
instance FromField CustomData where fromField = fromBlobField_ J.eitherDecodeStrict
|
||||
|
||||
contactConn :: Contact -> Maybe Connection
|
||||
contactConn Contact {activeConn} = activeConn
|
||||
|
||||
@@ -371,8 +356,7 @@ data GroupInfo = GroupInfo
|
||||
createdAt :: UTCTime,
|
||||
updatedAt :: UTCTime,
|
||||
chatTs :: Maybe UTCTime,
|
||||
userMemberProfileSentAt :: Maybe UTCTime,
|
||||
customData :: Maybe CustomData
|
||||
userMemberProfileSentAt :: Maybe UTCTime
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module Simplex.Chat.Util (week, encryptFile, chunkSize, liftIOEither, shuffle) where
|
||||
module Simplex.Chat.Util (week, encryptFile, chunkSize, shuffle) where
|
||||
|
||||
import Control.Monad
|
||||
import Control.Monad.Except
|
||||
@@ -42,7 +42,3 @@ shuffle xs = map snd . sortBy (comparing fst) <$> mapM (\x -> (,x) <$> random) x
|
||||
where
|
||||
random :: IO Word16
|
||||
random = randomRIO (0, 65535)
|
||||
|
||||
liftIOEither :: (MonadIO m, MonadError e m) => IO (Either e a) -> m a
|
||||
liftIOEither a = liftIO a >>= liftEither
|
||||
{-# INLINE liftIOEither #-}
|
||||
|
||||
@@ -183,7 +183,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um]
|
||||
CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m]
|
||||
CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"]
|
||||
CRRcvFileDescrReady _ _ _ _ -> []
|
||||
CRRcvFileDescrReady _ _ -> []
|
||||
CRRcvFileDescrNotReady _ _ -> []
|
||||
CRRcvFileProgressXFTP {} -> []
|
||||
CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci
|
||||
CRRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft
|
||||
@@ -390,7 +391,6 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)]
|
||||
CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)]
|
||||
CRTimedAction _ _ -> []
|
||||
CRCustomChatResponse u r -> ttyUser' u $ [plain r]
|
||||
where
|
||||
ttyUser :: User -> [StyledString] -> [StyledString]
|
||||
ttyUser user@User {showNtfs, activeUser} ss
|
||||
@@ -1167,7 +1167,7 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} =
|
||||
]
|
||||
|
||||
viewContactInfo :: Contact -> Maybe ConnectionStats -> Maybe Profile -> [StyledString]
|
||||
viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn, customData} stats incognitoProfile =
|
||||
viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn} stats incognitoProfile =
|
||||
["contact ID: " <> sShow contactId]
|
||||
<> maybe [] viewConnectionStats stats
|
||||
<> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink
|
||||
@@ -1179,17 +1179,12 @@ viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, conta
|
||||
<> [viewConnectionVerified (contactSecurityCode ct)]
|
||||
<> ["quantum resistant end-to-end encryption" | contactPQEnabled ct == CR.PQEncOn]
|
||||
<> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn
|
||||
<> viewCustomData customData
|
||||
|
||||
viewGroupInfo :: GroupInfo -> GroupSummary -> [StyledString]
|
||||
viewGroupInfo GroupInfo {groupId, customData} s =
|
||||
viewGroupInfo GroupInfo {groupId} s =
|
||||
[ "group ID: " <> sShow groupId,
|
||||
"current members: " <> sShow (currentMembers s)
|
||||
]
|
||||
<> viewCustomData customData
|
||||
|
||||
viewCustomData :: Maybe CustomData -> [StyledString]
|
||||
viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> plain (LB.toStrict . J.encode $ J.Object v)])
|
||||
|
||||
viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString]
|
||||
viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias, contactLink}, activeConn} stats =
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ testOpts =
|
||||
testCoreOpts :: CoreChatOpts
|
||||
testCoreOpts =
|
||||
CoreChatOpts
|
||||
{ dbFilePrefix = "./simplex_v1",
|
||||
{ dbFilePrefix = undefined,
|
||||
dbKey = "",
|
||||
-- dbKey = "this is a pass-phrase to encrypt the database",
|
||||
smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"],
|
||||
|
||||
@@ -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