mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1af4cb0dcf | |||
| 1d83140c1d | |||
| 14f79df72f | |||
| 55a05ebe5c | |||
| ada8dc659a | |||
| 2457fa174e | |||
| 46bc947128 | |||
| 09b2819fe6 | |||
| 792ee522b5 | |||
| b542c5d0b2 | |||
| fd11c9d0b3 | |||
| 4f2838d329 | |||
| db89757e11 | |||
| db331a7ff1 | |||
| c9c47bd1a6 | |||
| c2eb510b4a | |||
| fba0478e50 | |||
| d951003191 | |||
| 1af513c548 | |||
| 9d4e37a27c | |||
| c71e40cc9d |
@@ -487,6 +487,11 @@ func setUserProtoServers(_ serverProtocol: ServerProtocol, servers: [ServerCfg])
|
||||
try await sendCommandOkResp(.apiSetUserProtoServers(userId: userId, serverProtocol: serverProtocol, servers: servers))
|
||||
}
|
||||
|
||||
func addKnownProtoServer(server: String) async throws {
|
||||
let userId = try currentUserId("addKnownProtoServer")
|
||||
try await sendCommandOkResp(.apiAddKnownProtoServer(userId: userId, server: server))
|
||||
}
|
||||
|
||||
func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFailure> {
|
||||
let userId = try currentUserId("testProtoServer")
|
||||
let r = await chatSendCmd(.apiTestProtoServer(userId: userId, server: server))
|
||||
@@ -541,6 +546,11 @@ func reconnectAllServers() async throws {
|
||||
try await sendCommandOkResp(.reconnectAllServers)
|
||||
}
|
||||
|
||||
func reconnectServer(smpServer: String) async throws {
|
||||
let userId = try currentUserId("reconnectServer")
|
||||
try await sendCommandOkResp(.reconnectServer(userId: userId, smpServer: smpServer))
|
||||
}
|
||||
|
||||
func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) async throws {
|
||||
try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings))
|
||||
}
|
||||
@@ -1334,6 +1344,24 @@ func apiGetVersion() throws -> CoreVersionInfo {
|
||||
throw r
|
||||
}
|
||||
|
||||
func getAgentServersSummary() throws -> PresentedServersSummary {
|
||||
let userId = try currentUserId("getAgentServersSummary")
|
||||
let r = chatSendCmdSync(.getAgentServersSummary(userId: userId))
|
||||
if case let .agentServersSummary(_, serversSummary) = r { return serversSummary }
|
||||
throw r
|
||||
}
|
||||
|
||||
func resetAgentServersStats() async throws {
|
||||
try await sendCommandOkResp(.resetAgentServersStats)
|
||||
}
|
||||
|
||||
func getAgentSubsSummary() throws -> SMPServerSubs {
|
||||
let userId = try currentUserId("getAgentSubsSummary")
|
||||
let r = chatSendCmdSync(.getAgentSubsSummary(userId: userId))
|
||||
if case let .agentSubsSummary(_, subsSummary) = r { return subsSummary }
|
||||
throw r
|
||||
}
|
||||
|
||||
private func currentUserId(_ funcName: String) throws -> Int64 {
|
||||
if let userId = ChatModel.shared.currentUser?.userId {
|
||||
return userId
|
||||
|
||||
@@ -115,9 +115,7 @@ struct ChatListView: View {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
if chatModel.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
@@ -131,15 +129,6 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleFilterButton() -> some View {
|
||||
Button {
|
||||
showUnreadAndFavorites = !showUnreadAndFavorites
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle" + (showUnreadAndFavorites ? ".fill" : ""))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var chatList: some View {
|
||||
let cs = filteredChats()
|
||||
ZStack {
|
||||
@@ -274,6 +263,66 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct SubsStatusIndicator: View {
|
||||
@State private var subs: SMPServerSubs = SMPServerSubs(ssActive: 0, ssPending: 0)
|
||||
@State private var timer: Timer? = nil
|
||||
@State private var timerCounter = 0
|
||||
@State private var showServersSummary = false
|
||||
|
||||
// Constants for the intervals
|
||||
let initialInterval: TimeInterval = 1.0
|
||||
let regularInterval: TimeInterval = 5.0
|
||||
let initialPhaseDuration: TimeInterval = 10.0 // Duration for initial phase in seconds
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
showServersSummary = true
|
||||
} label: {
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending, variableValueAsPercentage: true)
|
||||
}
|
||||
.onAppear {
|
||||
startInitialTimer()
|
||||
}
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
.sheet(isPresented: $showServersSummary) {
|
||||
ServersSummaryView()
|
||||
}
|
||||
}
|
||||
|
||||
private func startInitialTimer() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in
|
||||
getSubsSummary()
|
||||
timerCounter += 1
|
||||
// Switch to the regular timer after the initial phase
|
||||
if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) {
|
||||
switchToRegularTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchToRegularTimer() {
|
||||
timer?.invalidate()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in
|
||||
getSubsSummary()
|
||||
}
|
||||
}
|
||||
|
||||
func stopTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func getSubsSummary() {
|
||||
do {
|
||||
subs = try getAgentSubsSummary()
|
||||
} catch let error {
|
||||
logger.error("getAgentSubsSummary error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListSearchBar: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Binding var searchMode: Bool
|
||||
@@ -282,9 +331,9 @@ struct ChatListSearchBar: View {
|
||||
@Binding var searchShowingSimplexLink: Bool
|
||||
@Binding var searchChatFilteredBySimplexLink: String?
|
||||
@State private var ignoreSearchTextChange = false
|
||||
@State private var showScanCodeSheet = false
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
@@ -301,26 +350,6 @@ struct ChatListSearchBar: View {
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
} else if !searchFocussed {
|
||||
HStack(spacing: 24) {
|
||||
if m.pasteboardHasStrings {
|
||||
Image(systemName: "doc")
|
||||
.onTapGesture {
|
||||
if let str = UIPasteboard.general.string {
|
||||
searchText = str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image(systemName: "qrcode")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture {
|
||||
showScanCodeSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 2)
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
@@ -335,14 +364,12 @@ struct ChatListSearchBar: View {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.sheet(isPresented: $showScanCodeSheet) {
|
||||
NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil) // fixes .refreshable in ChatListView affecting nested view
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
}
|
||||
@@ -376,6 +403,21 @@ struct ChatListSearchBar: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleFilterButton() -> some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
.frame(width: 22, height: 22)
|
||||
Image(systemName: showUnreadAndFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(showUnreadAndFavorites ? .accentColor : .secondary)
|
||||
.frame(width: showUnreadAndFavorites ? 22 : 16, height: showUnreadAndFavorites ? 22 : 16)
|
||||
.onTapGesture {
|
||||
showUnreadAndFavorites = !showUnreadAndFavorites
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ link: String) {
|
||||
planAndConnect(
|
||||
link,
|
||||
|
||||
@@ -0,0 +1,629 @@
|
||||
//
|
||||
// ServersSummaryView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 25.06.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ServersSummaryView: View {
|
||||
@State private var serversSummary: PresentedServersSummary? = nil
|
||||
@State private var selectedUserCategory: PresentedUserCategory = .allUsers
|
||||
@State private var selectedServerType: PresentedServerType = .smp
|
||||
@State private var selectedSMPServer: String? = nil
|
||||
@State private var selectedXFTPServer: String? = nil
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
enum PresentedUserCategory {
|
||||
case currentUser
|
||||
case allUsers
|
||||
}
|
||||
|
||||
enum PresentedServerType {
|
||||
case smp
|
||||
case xftp
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
viewBody()
|
||||
.navigationTitle("Servers info")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
reloadButton()
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
shareButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
getServersSummary()
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func shareButton() -> some View {
|
||||
Button {
|
||||
if let serversSummary = serversSummary {
|
||||
showShareSheet(items: [encodePrettyPrinted(serversSummary)])
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.disabled(serversSummary == nil)
|
||||
}
|
||||
|
||||
public func encodePrettyPrinted<T: Encodable>(_ value: T) -> String {
|
||||
let encoder = jsonEncoder
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
let data = try! encoder.encode(value)
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
private func reloadButton() -> some View {
|
||||
Button {
|
||||
getServersSummary()
|
||||
} label: {
|
||||
Image(systemName: "arrow.counterclockwise")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if let summ = serversSummary {
|
||||
List {
|
||||
Group {
|
||||
Picker("User selection", selection: $selectedUserCategory) {
|
||||
Text("All users").tag(PresentedUserCategory.allUsers)
|
||||
Text("Current user").tag(PresentedUserCategory.currentUser)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
Picker("Server type", selection: $selectedServerType) {
|
||||
Text("SMP").tag(PresentedServerType.smp)
|
||||
Text("XFTP").tag(PresentedServerType.xftp)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
switch (selectedUserCategory, selectedServerType) {
|
||||
case (.allUsers, .smp):
|
||||
if summ.allUsedSMP.count > 0 || summ.allPrevSMP.count > 0 || summ.allProxSMP.count > 0 {
|
||||
if summ.allUsedSMP.count > 0 {
|
||||
smpServersListView(summ.allUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevSMP.count > 0 {
|
||||
smpServersListView(summ.allPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.allProxSMP.count > 0 {
|
||||
smpServersListView(summ.allProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.currentUser, .smp):
|
||||
if summ.userUsedSMP.count > 0 || summ.userPrevSMP.count > 0 || summ.userProxSMP.count > 0 {
|
||||
if summ.userUsedSMP.count > 0 {
|
||||
smpServersListView(summ.userUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevSMP.count > 0 {
|
||||
smpServersListView(summ.userPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.userProxSMP.count > 0 {
|
||||
smpServersListView(summ.userProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.allUsers, .xftp):
|
||||
if summ.allUsedXFTP.count > 0 || summ.allPrevXFTP.count > 0 {
|
||||
if summ.allUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.allUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.allPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.currentUser, .xftp):
|
||||
if summ.userUsedXFTP.count > 0 || summ.userPrevXFTP.count > 0 {
|
||||
if summ.userUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.userUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.userPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No info, try to reload")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func smpServersListView(
|
||||
_ servers: [SMPServerSummary],
|
||||
showReconnectButton: Bool,
|
||||
_ statsStartedAt: Date,
|
||||
_ header: LocalizedStringKey? = nil,
|
||||
_ footer: LocalizedStringKey? = nil
|
||||
) -> some View {
|
||||
let sortedServers = servers.sorted { serverAddress($0.smpServer).compare(serverAddress($1.smpServer)) == .orderedAscending }
|
||||
Section {
|
||||
ForEach(sortedServers) { server in
|
||||
smpServerView(server, showReconnectButton, statsStartedAt)
|
||||
}
|
||||
} header: {
|
||||
if let header = header {
|
||||
Text(header)
|
||||
}
|
||||
} footer: {
|
||||
if let footer = footer {
|
||||
Text(footer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func smpServerView(_ server: SMPServerSummary, _ showReconnectButton: Bool, _ statsStartedAt: Date) -> some View {
|
||||
NavigationLink(tag: server.id, selection: $selectedSMPServer) {
|
||||
SMPServerSummaryView(
|
||||
summary: server,
|
||||
showReconnectButton: showReconnectButton,
|
||||
statsStartedAt: statsStartedAt
|
||||
)
|
||||
.navigationBarTitle("SMP server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
HStack {
|
||||
if let subs = server.subs {
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending)
|
||||
.frame(width: 16, alignment: .center)
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
Text(serverAddress(server.smpServer))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func serverAddress(_ server: String) -> String {
|
||||
parseServerAddress(server)?.hostnames.first ?? server
|
||||
}
|
||||
|
||||
@ViewBuilder private func xftpServersListView(
|
||||
_ servers: [XFTPServerSummary],
|
||||
_ statsStartedAt: Date,
|
||||
_ header: LocalizedStringKey? = nil,
|
||||
_ footer: LocalizedStringKey? = nil
|
||||
) -> some View {
|
||||
let sortedServers = servers.sorted { serverAddress($0.xftpServer).compare(serverAddress($1.xftpServer)) == .orderedAscending }
|
||||
Section {
|
||||
ForEach(sortedServers) { server in
|
||||
xftpServerView(server, statsStartedAt)
|
||||
}
|
||||
} header: {
|
||||
if let header = header {
|
||||
Text(header)
|
||||
}
|
||||
} footer: {
|
||||
if let footer = footer {
|
||||
Text(footer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func xftpServerView(_ server: XFTPServerSummary, _ statsStartedAt: Date) -> some View {
|
||||
NavigationLink(tag: server.id, selection: $selectedXFTPServer) {
|
||||
XFTPServerSummaryView(
|
||||
summary: server,
|
||||
statsStartedAt: statsStartedAt
|
||||
)
|
||||
.navigationBarTitle("XFTP server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Text(serverAddress(server.xftpServer))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func noCategoryInfoText() -> some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.foregroundColor(Color.clear)
|
||||
Text("No info")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
|
||||
private func resetStatsButtonSection() -> some View {
|
||||
Section {
|
||||
Button {
|
||||
alert = SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Reset servers statistics?"),
|
||||
message: Text("Servers statistics will be reset - this cannot be undone!"),
|
||||
primaryButton: .destructive(Text("Reset")) {
|
||||
Task {
|
||||
do {
|
||||
try await resetAgentServersStats()
|
||||
getServersSummary()
|
||||
} catch let error {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error resetting statistics",
|
||||
message: "\(responseError(error))"
|
||||
),
|
||||
id: "error resetting statistics"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
),
|
||||
id: "reset statistics question"
|
||||
)
|
||||
} label: {
|
||||
Text("Reset statistics")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getServersSummary() {
|
||||
do {
|
||||
serversSummary = try getAgentServersSummary()
|
||||
} catch let error {
|
||||
logger.error("getAgentServersSummary error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SubscriptionStatusView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var activeSubs: Int
|
||||
var pendingSubs: Int
|
||||
var variableValueAsPercentage: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let netInfo = m.networkInfo
|
||||
if netInfo.online {
|
||||
let (image, color, variableValue, opacity) = networkOnlineImage(netInfo.networkType)
|
||||
if #available(iOS 16.0, *) {
|
||||
Image(systemName: image, variableValue: variableValue)
|
||||
.foregroundColor(color)
|
||||
} else {
|
||||
Image(systemName: image)
|
||||
.foregroundColor(color.opacity(opacity))
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "wifi.slash")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
func networkOnlineImage(_ networkType: UserNetworkType) -> (String, Color, Double, Double) {
|
||||
switch networkType {
|
||||
case .cellular:
|
||||
let (color, variableValue, opacity) = cellularbarsColor
|
||||
return ("cellularbars", color, variableValue, opacity)
|
||||
default:
|
||||
let (color, variableValue, opacity) = wifiColor
|
||||
return ("wifi", color, variableValue, opacity)
|
||||
}
|
||||
}
|
||||
|
||||
// We manipulate variableValue so all "wifi" sections are filled only with 100% active subs,
|
||||
// unless variableValueAsPercentage is true; same for cellularbarsColor
|
||||
var wifiColor: (Color, Double, Double) {
|
||||
if activeSubs > 0 {
|
||||
let wifiVariableValue = (
|
||||
variableValueAsPercentage ? activeSubsPercentage
|
||||
: ( // "wifi" has 3 sections
|
||||
activeSubsPercentage >= 1 ? 1
|
||||
: (activeSubsPercentage >= 0.5 && activeSubsPercentage < 1) ? 0.6
|
||||
: (activeSubsPercentage > 0 && activeSubsPercentage < 0.5) ? 0.3
|
||||
: 0
|
||||
)
|
||||
)
|
||||
return (.accentColor, wifiVariableValue, activeSubsPercentage)
|
||||
} else {
|
||||
return (.secondary, 1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
var cellularbarsColor: (Color, Double, Double) {
|
||||
if activeSubs > 0 {
|
||||
let wifiVariableValue = (
|
||||
variableValueAsPercentage ? activeSubsPercentage
|
||||
: ( // "cellularbars" has 4 sections
|
||||
activeSubsPercentage >= 1 ? 1
|
||||
: (activeSubsPercentage >= 0.67 && activeSubsPercentage < 1) ? 0.7
|
||||
: (activeSubsPercentage >= 0.33 && activeSubsPercentage < 0.67) ? 0.45
|
||||
: (activeSubsPercentage > 0 && activeSubsPercentage < 0.33) ? 0.2
|
||||
: 0
|
||||
)
|
||||
)
|
||||
return (.accentColor, wifiVariableValue, activeSubsPercentage)
|
||||
} else {
|
||||
return (.secondary, 1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
var activeSubsPercentage: Double {
|
||||
let total = activeSubs + pendingSubs
|
||||
guard total != 0 else { return 0.0 }
|
||||
return Double(activeSubs) / Double(total)
|
||||
}
|
||||
}
|
||||
|
||||
struct SMPServerSummaryView: View {
|
||||
var summary: SMPServerSummary
|
||||
var showReconnectButton: Bool
|
||||
var statsStartedAt: Date
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Text(summary.smpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
addKnownServer()
|
||||
} label: {
|
||||
Text("Add as known")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
if let known = summary.known, known {
|
||||
Text("Server is configured in **Settings** → **Network & servers**.")
|
||||
}
|
||||
}
|
||||
|
||||
if showReconnectButton {
|
||||
reconnectButtonSection()
|
||||
}
|
||||
|
||||
if let subs = summary.subs {
|
||||
subsSection(subs)
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
}
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func reconnectButtonSection() -> some View {
|
||||
Section {
|
||||
Button {
|
||||
alert = SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Reconnect server?"),
|
||||
message: Text("Reconnect server to force message delivery. It uses additional traffic."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
Task {
|
||||
do {
|
||||
try await reconnectServer(smpServer: summary.smpServer)
|
||||
} catch let error {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error reconnecting server",
|
||||
message: "\(responseError(error))"
|
||||
),
|
||||
id: "error reconnecting server"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
),
|
||||
id: "reconnect server question"
|
||||
)
|
||||
} label: {
|
||||
Text("Reconnect")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func subsSection(_ subs: SMPServerSubs) -> some View {
|
||||
Section {
|
||||
infoRow("Active", "\(subs.ssActive)")
|
||||
infoRow("Pending", "\(subs.ssPending)")
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Subscriptions")
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentSMPServerStatsData) -> some View {
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Messages sent directly", "\(stats._sentDirect)")
|
||||
indentedInfoRow("attempts", "\(stats._sentDirectAttempts)")
|
||||
infoRow("Messages sent via proxy", "\(stats._sentViaProxy)")
|
||||
indentedInfoRow("attempts", "\(stats._sentViaProxyAttempts)")
|
||||
infoRow("Messages sent to proxy", "\(stats._sentProxied)")
|
||||
indentedInfoRow("attempts", "\(stats._sentProxiedAttempts)")
|
||||
infoRow("Sending AUTH errors", "\(stats._sentAuthErrs)")
|
||||
indentedInfoRow("QUOTA errors", "\(stats._sentQuotaErrs)")
|
||||
indentedInfoRow("expired", "\(stats._sentExpiredErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._sentOtherErrs)")
|
||||
infoRow("Messages received", "\(stats._recvMsgs)")
|
||||
indentedInfoRow("duplicates", "\(stats._recvDuplicates)")
|
||||
indentedInfoRow("decryption errors", "\(stats._recvCryptoErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._recvErrs)")
|
||||
infoRow("Messages acknowledged", "\(stats._ackMsgs)")
|
||||
indentedInfoRow("attempts", "\(stats._ackAttempts)")
|
||||
infoRow("Connections created", "\(stats._connCreated)")
|
||||
indentedInfoRow("secured", "\(stats._connSecured)")
|
||||
indentedInfoRow("completed", "\(stats._connCompleted)")
|
||||
infoRow("Connections deleted", "\(stats._connDeleted)")
|
||||
infoRow("Connections subscribed", "\(stats._connSubscribed)")
|
||||
indentedInfoRow("attempts", "\(stats._connSubAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._connSubErrs)")
|
||||
}
|
||||
}
|
||||
|
||||
func addKnownServer() {
|
||||
Task {
|
||||
do {
|
||||
try await addKnownProtoServer(server: summary.smpServer)
|
||||
await MainActor.run {
|
||||
// TODO disable button, etc.
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
alert = errorAddingServerAlert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func indentedInfoRow(_ title: LocalizedStringKey, _ value: String) -> some View {
|
||||
HStack {
|
||||
Text(title)
|
||||
.padding(.leading, 24)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private func errorAddingServerAlert(_ error: Error) -> SomeAlert {
|
||||
SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error adding server",
|
||||
message: "Make sure server address is in correct format (\(responseError(error)))."
|
||||
),
|
||||
id: "error saving server"
|
||||
)
|
||||
}
|
||||
|
||||
struct XFTPServerSummaryView: View {
|
||||
var summary: XFTPServerSummary
|
||||
var statsStartedAt: Date
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Text(summary.xftpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
addKnownServer()
|
||||
} label: {
|
||||
Text("Add as known")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
if let known = summary.known, known {
|
||||
Text("Server is configured in **Settings** → **Network & servers**.")
|
||||
}
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
}
|
||||
|
||||
inProgressSection()
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
|
||||
private func inProgressSection() -> some View {
|
||||
Section("In progress") {
|
||||
localizedInfoRow("Download", boolYesNo(summary.rcvInProgress))
|
||||
localizedInfoRow("Upload", boolYesNo(summary.sndInProgress))
|
||||
localizedInfoRow("Deletion", boolYesNo(summary.delInProgress))
|
||||
}
|
||||
}
|
||||
|
||||
private func boolYesNo(_ b: Bool) -> LocalizedStringKey {
|
||||
b ? "yes" : "no"
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentXFTPServerStatsData) -> some View {
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Chunks uploaded", "\(stats._uploads)")
|
||||
indentedInfoRow("attempts", "\(stats._uploadAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._uploadErrs)")
|
||||
infoRow("Chunks downloaded", "\(stats._downloads)")
|
||||
indentedInfoRow("attempts", "\(stats._downloadAttempts)")
|
||||
indentedInfoRow("AUTH errors", "\(stats._downloadAuthErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._downloadErrs)")
|
||||
infoRow("Chunks deleted", "\(stats._deletions)")
|
||||
indentedInfoRow("attempts", "\(stats._deleteAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._deleteErrs)")
|
||||
}
|
||||
}
|
||||
|
||||
func addKnownServer() {
|
||||
Task {
|
||||
do {
|
||||
try await addKnownProtoServer(server: summary.xftpServer)
|
||||
await MainActor.run {
|
||||
// TODO disable button, etc.
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
alert = errorAddingServerAlert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ServersSummaryView()
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
|
||||
enum NewChatMenuOption: Identifiable {
|
||||
case newContact
|
||||
case scanPaste
|
||||
case newGroup
|
||||
|
||||
var id: Self { self }
|
||||
@@ -25,6 +26,11 @@ struct NewChatMenuButton: View {
|
||||
} label: {
|
||||
Text("Add contact")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .scanPaste
|
||||
} label: {
|
||||
Text("Scan / Paste link")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .newGroup
|
||||
} label: {
|
||||
@@ -39,6 +45,7 @@ struct NewChatMenuButton: View {
|
||||
.sheet(item: $newChatMenuOption) { opt in
|
||||
switch opt {
|
||||
case .newContact: NewChatView(selection: .invite)
|
||||
case .scanPaste: NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
case .newGroup: AddGroupView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ struct ProtocolServerView: View {
|
||||
let serverProtocol: ServerProtocol
|
||||
@Binding var server: ServerCfg
|
||||
@State var serverToEdit: ServerCfg
|
||||
@State var serverEnabled: Bool
|
||||
@State private var showTestFailure = false
|
||||
@State private var testing = false
|
||||
@State private var testFailure: ProtocolTestFailure?
|
||||
@@ -110,7 +111,10 @@ struct ProtocolServerView: View {
|
||||
Spacer()
|
||||
showTestStatus(server: serverToEdit)
|
||||
}
|
||||
Toggle("Use for new connections", isOn: $serverToEdit.enabled)
|
||||
Toggle("Use for new connections", isOn: $serverEnabled)
|
||||
.onChange(of: serverEnabled) { enabled in
|
||||
serverToEdit.enabled = enabled ? .enabled : .disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +183,8 @@ struct ProtocolServerView_Previews: PreviewProvider {
|
||||
ProtocolServerView(
|
||||
serverProtocol: .smp,
|
||||
server: Binding.constant(ServerCfg.sampleData.custom),
|
||||
serverToEdit: ServerCfg.sampleData.custom
|
||||
serverToEdit: ServerCfg.sampleData.custom,
|
||||
serverEnabled: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ struct ProtocolServersView: View {
|
||||
}
|
||||
|
||||
private var allServersDisabled: Bool {
|
||||
servers.allSatisfy { !$0.enabled }
|
||||
servers.allSatisfy { $0.enabled != .enabled }
|
||||
}
|
||||
|
||||
private func protocolServerView(_ server: Binding<ServerCfg>) -> some View {
|
||||
@@ -168,7 +168,8 @@ struct ProtocolServersView: View {
|
||||
ProtocolServerView(
|
||||
serverProtocol: serverProtocol,
|
||||
server: server,
|
||||
serverToEdit: srv
|
||||
serverToEdit: srv,
|
||||
serverEnabled: srv.enabled == .enabled
|
||||
)
|
||||
.navigationBarTitle(srv.preset ? "Preset server" : "Your server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
@@ -181,7 +182,7 @@ struct ProtocolServersView: View {
|
||||
invalidServer()
|
||||
} else if !uniqueAddress(srv, address) {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
} else if !srv.enabled {
|
||||
} else if srv.enabled != .enabled {
|
||||
Image(systemName: "slash.circle").foregroundColor(.secondary)
|
||||
} else {
|
||||
showTestStatus(server: srv)
|
||||
@@ -194,7 +195,7 @@ struct ProtocolServersView: View {
|
||||
.padding(.trailing, 4)
|
||||
|
||||
let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1)
|
||||
if srv.enabled {
|
||||
if srv.enabled == .enabled {
|
||||
v
|
||||
} else {
|
||||
v.foregroundColor(.secondary)
|
||||
@@ -235,7 +236,7 @@ struct ProtocolServersView: View {
|
||||
private func addAllPresets() {
|
||||
for srv in presetServers {
|
||||
if !hasPreset(srv) {
|
||||
servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: true))
|
||||
servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: .enabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +261,7 @@ struct ProtocolServersView: View {
|
||||
|
||||
private func resetTestStatus() {
|
||||
for i in 0..<servers.count {
|
||||
if servers[i].enabled {
|
||||
if servers[i].enabled == .enabled {
|
||||
servers[i].tested = nil
|
||||
}
|
||||
}
|
||||
@@ -269,7 +270,7 @@ struct ProtocolServersView: View {
|
||||
private func runServersTest() async -> [String: ProtocolTestFailure] {
|
||||
var fs: [String: ProtocolTestFailure] = [:]
|
||||
for i in 0..<servers.count {
|
||||
if servers[i].enabled {
|
||||
if servers[i].enabled == .enabled {
|
||||
if let f = await testServerConnection(server: $servers[i]) {
|
||||
fs[serverHostname(servers[i].server)] = f
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ScanProtocolServer: View {
|
||||
switch resp {
|
||||
case let .success(r):
|
||||
if parseServerAddress(r.string) != nil {
|
||||
servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: true))
|
||||
servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: .enabled))
|
||||
dismiss()
|
||||
} else {
|
||||
showAddressError = true
|
||||
|
||||
@@ -148,6 +148,12 @@
|
||||
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; };
|
||||
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; };
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
|
||||
641753572C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
|
||||
641753582C2AC158005415B4 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753532C2AC158005415B4 /* libgmp.a */; };
|
||||
641753592C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
|
||||
6417535A2C2AC158005415B4 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753552C2AC158005415B4 /* libgmpxx.a */; };
|
||||
6417535B2C2AC158005415B4 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753562C2AC158005415B4 /* libffi.a */; };
|
||||
6417535D2C2ACD77005415B4 /* ServersSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */; };
|
||||
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
|
||||
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
|
||||
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
|
||||
@@ -158,11 +164,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 */; };
|
||||
@@ -197,11 +198,6 @@
|
||||
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3B2C22D78C00CBA347 /* libffi.a */; };
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3D2C22D78C00CBA347 /* libgmp.a */; };
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -448,6 +444,12 @@
|
||||
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
|
||||
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
|
||||
641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
641753532C2AC158005415B4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
|
||||
641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; path = "Libraries/libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
|
||||
641753552C2AC158005415B4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
|
||||
641753562C2AC158005415B4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
|
||||
6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSummaryView.swift; sourceTree = "<group>"; };
|
||||
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
|
||||
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
||||
@@ -458,11 +460,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>"; };
|
||||
@@ -497,11 +494,6 @@
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -539,13 +531,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
|
||||
6417535A2C2AC158005415B4 /* libgmpxx.a in Frameworks */,
|
||||
641753592C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
641753572C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
|
||||
6417535B2C2AC158005415B4 /* libffi.a in Frameworks */,
|
||||
641753582C2AC158005415B4 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */,
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */,
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -611,11 +603,6 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */,
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */,
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */,
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -682,6 +669,11 @@
|
||||
5CC2C0FA2809BF11000C35E3 /* Localizable.strings */,
|
||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */,
|
||||
5C764E5C279C70B7000C6508 /* Libraries */,
|
||||
641753562C2AC158005415B4 /* libffi.a */,
|
||||
641753532C2AC158005415B4 /* libgmp.a */,
|
||||
641753552C2AC158005415B4 /* libgmpxx.a */,
|
||||
641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
|
||||
641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
|
||||
5CA059C2279559F40002BEB4 /* Shared */,
|
||||
5CDCAD462818589900503DA2 /* SimpleX NSE */,
|
||||
5CA059DA279559F40002BEB4 /* Tests iOS */,
|
||||
@@ -812,6 +804,7 @@
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */,
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */,
|
||||
18415835CBD939A9ABDC108A /* UserPicker.swift */,
|
||||
6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */,
|
||||
);
|
||||
path = ChatList;
|
||||
sourceTree = "<group>";
|
||||
@@ -1266,6 +1259,7 @@
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */,
|
||||
5C9329412929248A0090FFF9 /* ScanProtocolServer.swift in Sources */,
|
||||
8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */,
|
||||
6417535D2C2ACD77005415B4 /* ServersSummaryView.swift in Sources */,
|
||||
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */,
|
||||
5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */,
|
||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */,
|
||||
@@ -1562,7 +1556,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1587,7 +1581,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1611,7 +1605,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1636,7 +1630,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1697,7 +1691,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -1712,7 +1706,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1734,7 +1728,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -1749,7 +1743,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1771,7 +1765,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1797,7 +1791,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1822,7 +1816,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1848,7 +1842,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public let jsonDecoder = getJSONDecoder()
|
||||
let jsonEncoder = getJSONEncoder()
|
||||
public let jsonEncoder = getJSONEncoder()
|
||||
|
||||
public enum ChatCommand {
|
||||
case showActiveUser
|
||||
@@ -71,6 +71,7 @@ public enum ChatCommand {
|
||||
case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent)
|
||||
case apiGetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol)
|
||||
case apiSetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol, servers: [ServerCfg])
|
||||
case apiAddKnownProtoServer(userId: Int64, server: String)
|
||||
case apiTestProtoServer(userId: Int64, server: String)
|
||||
case apiSetChatItemTTL(userId: Int64, seconds: Int64?)
|
||||
case apiGetChatItemTTL(userId: Int64)
|
||||
@@ -78,6 +79,7 @@ public enum ChatCommand {
|
||||
case apiGetNetworkConfig
|
||||
case apiSetNetworkInfo(networkInfo: UserNetworkInfo)
|
||||
case reconnectAllServers
|
||||
case reconnectServer(userId: Int64, smpServer: String)
|
||||
case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings)
|
||||
case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings)
|
||||
case apiContactInfo(contactId: Int64)
|
||||
@@ -122,6 +124,7 @@ public enum ChatCommand {
|
||||
case apiEndCall(contact: Contact)
|
||||
case apiGetCallInvitations
|
||||
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
||||
// WebRTC calls /
|
||||
case apiGetNetworkStatuses
|
||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||
@@ -142,6 +145,9 @@ public enum ChatCommand {
|
||||
case apiStandaloneFileInfo(url: String)
|
||||
// misc
|
||||
case showVersion
|
||||
case getAgentServersSummary(userId: Int64)
|
||||
case resetAgentServersStats
|
||||
case getAgentSubsSummary(userId: Int64)
|
||||
case string(String)
|
||||
|
||||
public var cmdString: String {
|
||||
@@ -219,6 +225,7 @@ public enum ChatCommand {
|
||||
case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)"
|
||||
case let .apiGetUserProtoServers(userId, serverProtocol): return "/_servers \(userId) \(serverProtocol)"
|
||||
case let .apiSetUserProtoServers(userId, serverProtocol, servers): return "/_servers \(userId) \(serverProtocol) \(protoServersStr(servers))"
|
||||
case let .apiAddKnownProtoServer(userId, server): return "/_known server \(userId) \(server)"
|
||||
case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)"
|
||||
case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))"
|
||||
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
|
||||
@@ -226,6 +233,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "/network"
|
||||
case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))"
|
||||
case .reconnectAllServers: return "/reconnect"
|
||||
case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)"
|
||||
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
|
||||
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
|
||||
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
|
||||
@@ -301,6 +309,9 @@ public enum ChatCommand {
|
||||
case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)"
|
||||
case let .apiStandaloneFileInfo(link): return "/_download info \(link)"
|
||||
case .showVersion: return "/version"
|
||||
case let .getAgentServersSummary(userId): return "/get servers summary \(userId)"
|
||||
case .resetAgentServersStats: return "/reset servers stats"
|
||||
case let .getAgentSubsSummary(userId): return "/get subs summary \(userId)"
|
||||
case let .string(str): return str
|
||||
}
|
||||
}
|
||||
@@ -368,6 +379,7 @@ public enum ChatCommand {
|
||||
case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation"
|
||||
case .apiGetUserProtoServers: return "apiGetUserProtoServers"
|
||||
case .apiSetUserProtoServers: return "apiSetUserProtoServers"
|
||||
case .apiAddKnownProtoServer: return "apiAddKnownProtoServer"
|
||||
case .apiTestProtoServer: return "apiTestProtoServer"
|
||||
case .apiSetChatItemTTL: return "apiSetChatItemTTL"
|
||||
case .apiGetChatItemTTL: return "apiGetChatItemTTL"
|
||||
@@ -375,6 +387,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "apiGetNetworkConfig"
|
||||
case .apiSetNetworkInfo: return "apiSetNetworkInfo"
|
||||
case .reconnectAllServers: return "reconnectAllServers"
|
||||
case .reconnectServer: return "reconnectServer"
|
||||
case .apiSetChatSettings: return "apiSetChatSettings"
|
||||
case .apiSetMemberSettings: return "apiSetMemberSettings"
|
||||
case .apiContactInfo: return "apiContactInfo"
|
||||
@@ -435,6 +448,9 @@ public enum ChatCommand {
|
||||
case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile"
|
||||
case .apiStandaloneFileInfo: return "apiStandaloneFileInfo"
|
||||
case .showVersion: return "showVersion"
|
||||
case .getAgentServersSummary: return "getAgentServersSummary"
|
||||
case .resetAgentServersStats: return "resetAgentServersStats"
|
||||
case .getAgentSubsSummary: return "getAgentSubsSummary"
|
||||
case .string: return "console command"
|
||||
}
|
||||
}
|
||||
@@ -663,6 +679,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
// misc
|
||||
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
|
||||
case cmdOk(user: UserRef?)
|
||||
case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary)
|
||||
case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs)
|
||||
case chatCmdError(user_: UserRef?, chatError: ChatError)
|
||||
case chatError(user_: UserRef?, chatError: ChatError)
|
||||
case archiveImported(archiveErrors: [ArchiveError])
|
||||
@@ -821,6 +839,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactPQEnabled: return "contactPQEnabled"
|
||||
case .versionInfo: return "versionInfo"
|
||||
case .cmdOk: return "cmdOk"
|
||||
case .agentServersSummary: return "agentServersSummary"
|
||||
case .agentSubsSummary: return "agentSubsSummary"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
case .chatError: return "chatError"
|
||||
case .archiveImported: return "archiveImported"
|
||||
@@ -984,6 +1004,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)")
|
||||
case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))"
|
||||
case .cmdOk: return noDetails
|
||||
case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary))
|
||||
case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary))
|
||||
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .archiveImported(archiveErrors): return String(describing: archiveErrors)
|
||||
@@ -1109,13 +1131,13 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
public var server: String
|
||||
public var preset: Bool
|
||||
public var tested: Bool?
|
||||
public var enabled: Bool
|
||||
public var enabled: ServerEnabled
|
||||
var createdAt = Date()
|
||||
// public var sendEnabled: Bool // can we potentially want to prevent sending on the servers we use to receive?
|
||||
// Even if we don't see the use case, it's probably better to allow it in the model
|
||||
// In any case, "trusted/known" servers are out of scope of this change
|
||||
|
||||
public init(server: String, preset: Bool, tested: Bool?, enabled: Bool) {
|
||||
public init(server: String, preset: Bool, tested: Bool?, enabled: ServerEnabled) {
|
||||
self.server = server
|
||||
self.preset = preset
|
||||
self.tested = tested
|
||||
@@ -1128,7 +1150,7 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
|
||||
public var id: String { "\(server) \(createdAt)" }
|
||||
|
||||
public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: true)
|
||||
public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: .enabled)
|
||||
|
||||
public var isEmpty: Bool {
|
||||
server.trimmingCharacters(in: .whitespaces) == ""
|
||||
@@ -1145,19 +1167,19 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
server: "smp://abcd@smp8.simplex.im",
|
||||
preset: true,
|
||||
tested: true,
|
||||
enabled: true
|
||||
enabled: .enabled
|
||||
),
|
||||
custom: ServerCfg(
|
||||
server: "smp://abcd@smp9.simplex.im",
|
||||
preset: false,
|
||||
tested: false,
|
||||
enabled: false
|
||||
enabled: .enabled
|
||||
),
|
||||
untested: ServerCfg(
|
||||
server: "smp://abcd@smp10.simplex.im",
|
||||
preset: false,
|
||||
tested: nil,
|
||||
enabled: true
|
||||
enabled: .enabled
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1169,6 +1191,12 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServerEnabled: String, Codable {
|
||||
case disabled
|
||||
case enabled
|
||||
case known
|
||||
}
|
||||
|
||||
public enum ProtocolTestStep: String, Decodable, Equatable {
|
||||
case connect
|
||||
case disconnect
|
||||
@@ -2224,3 +2252,108 @@ public enum MsgType: String, Codable {
|
||||
case message
|
||||
case quota
|
||||
}
|
||||
|
||||
public struct PresentedServersSummary: Codable {
|
||||
public var statsStartedAt: Date
|
||||
public var currentUserServers: ServersSummary
|
||||
public var allUsersServers: ServersSummary
|
||||
|
||||
public var allUsedSMP: [SMPServerSummary] { self.allUsersServers.currentlyUsedSMPServers }
|
||||
public var allPrevSMP: [SMPServerSummary] { self.allUsersServers.previouslyUsedSMPServers }
|
||||
public var allProxSMP: [SMPServerSummary] { self.allUsersServers.onlyProxiedSMPServers }
|
||||
|
||||
public var userUsedSMP: [SMPServerSummary] { self.currentUserServers.currentlyUsedSMPServers }
|
||||
public var userPrevSMP: [SMPServerSummary] { self.currentUserServers.previouslyUsedSMPServers }
|
||||
public var userProxSMP: [SMPServerSummary] { self.currentUserServers.onlyProxiedSMPServers }
|
||||
|
||||
public var allUsedXFTP: [XFTPServerSummary] { self.allUsersServers.currentlyUsedXFTPServers }
|
||||
public var allPrevXFTP: [XFTPServerSummary] { self.allUsersServers.previouslyUsedXFTPServers }
|
||||
|
||||
public var userUsedXFTP: [XFTPServerSummary] { self.currentUserServers.currentlyUsedXFTPServers }
|
||||
public var userPrevXFTP: [XFTPServerSummary] { self.currentUserServers.previouslyUsedXFTPServers }
|
||||
}
|
||||
|
||||
public struct ServersSummary: Codable {
|
||||
public var currentlyUsedSMPServers: [SMPServerSummary]
|
||||
public var previouslyUsedSMPServers: [SMPServerSummary]
|
||||
public var onlyProxiedSMPServers: [SMPServerSummary]
|
||||
public var currentlyUsedXFTPServers: [XFTPServerSummary]
|
||||
public var previouslyUsedXFTPServers: [XFTPServerSummary]
|
||||
}
|
||||
|
||||
public struct SMPServerSummary: Codable, Identifiable {
|
||||
public var smpServer: String
|
||||
public var known: Bool?
|
||||
public var sessions: ServerSessions?
|
||||
public var subs: SMPServerSubs?
|
||||
public var stats: AgentSMPServerStatsData?
|
||||
|
||||
public var id: String { smpServer }
|
||||
}
|
||||
|
||||
public struct ServerSessions: Codable {
|
||||
public var ssConnected: Int
|
||||
public var ssErrors: Int
|
||||
public var ssConnecting: Int
|
||||
}
|
||||
|
||||
public struct SMPServerSubs: Codable {
|
||||
public var ssActive: Int
|
||||
public var ssPending: Int
|
||||
|
||||
public init(ssActive: Int, ssPending: Int) {
|
||||
self.ssActive = ssActive
|
||||
self.ssPending = ssPending
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentSMPServerStatsData: Codable {
|
||||
public var _sentDirect: Int
|
||||
public var _sentViaProxy: Int
|
||||
public var _sentProxied: Int
|
||||
public var _sentDirectAttempts: Int
|
||||
public var _sentViaProxyAttempts: Int
|
||||
public var _sentProxiedAttempts: Int
|
||||
public var _sentAuthErrs: Int
|
||||
public var _sentQuotaErrs: Int
|
||||
public var _sentExpiredErrs: Int
|
||||
public var _sentOtherErrs: Int
|
||||
public var _recvMsgs: Int
|
||||
public var _recvDuplicates: Int
|
||||
public var _recvCryptoErrs: Int
|
||||
public var _recvErrs: Int
|
||||
public var _ackMsgs: Int
|
||||
public var _ackAttempts: Int
|
||||
public var _connCreated: Int
|
||||
public var _connSecured: Int
|
||||
public var _connCompleted: Int
|
||||
public var _connDeleted: Int
|
||||
public var _connSubscribed: Int
|
||||
public var _connSubAttempts: Int
|
||||
public var _connSubErrs: Int
|
||||
}
|
||||
|
||||
public struct XFTPServerSummary: Codable, Identifiable {
|
||||
public var xftpServer: String
|
||||
public var known: Bool?
|
||||
public var sessions: ServerSessions?
|
||||
public var stats: AgentXFTPServerStatsData?
|
||||
public var rcvInProgress: Bool
|
||||
public var sndInProgress: Bool
|
||||
public var delInProgress: Bool
|
||||
|
||||
public var id: String { xftpServer }
|
||||
}
|
||||
|
||||
public struct AgentXFTPServerStatsData: Codable {
|
||||
public var _uploads: Int
|
||||
public var _uploadAttempts: Int
|
||||
public var _uploadErrs: Int
|
||||
public var _downloads: Int
|
||||
public var _downloadAttempts: Int
|
||||
public var _downloadAuthErrs: Int
|
||||
public var _downloadErrs: Int
|
||||
public var _deletions: Int
|
||||
public var _deleteAttempts: Int
|
||||
public var _deleteErrs: Int
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import SwiftUI
|
||||
|
||||
public struct User: Identifiable, Decodable, UserLike, NamedChat {
|
||||
public var userId: Int64
|
||||
public var agentUserId: String
|
||||
var userContactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
public var profile: LocalProfile
|
||||
@@ -41,6 +42,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat {
|
||||
|
||||
public static let sampleData = User(
|
||||
userId: 1,
|
||||
agentUserId: "abc",
|
||||
userContactId: 1,
|
||||
localDisplayName: "alice",
|
||||
profile: LocalProfile.sampleData,
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 8a3b72458f917e9867f4e3640dda0fa1827ff6cf
|
||||
tag: c7886926870e97fa592d51fa36a2cdec49296388
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -4,9 +4,16 @@ sequenceDiagram
|
||||
participant B as Bob
|
||||
participant C as Existing<br>contact
|
||||
|
||||
note over A, B: 1. send and accept group invitation
|
||||
A ->> B: x.grp.inv<br>invite Bob to group<br>(via contact connection)
|
||||
B ->> A: x.grp.acpt<br>accept invitation<br>(via member connection)<br>establish group member connection
|
||||
alt invite contact
|
||||
note over A, B: 1a. send and accept group invitation
|
||||
A ->> B: x.grp.inv<br>invite Bob to group<br>(via contact connection)
|
||||
B ->> A: x.grp.acpt<br>accept invitation<br>(via member connection)<br>establish group member connection
|
||||
else join via group link
|
||||
note over A, B: 1b. join via group link and accept request
|
||||
B ->> A: join via group link<br>SimpleX contact address
|
||||
A ->> B: x.grp.link.inv in SMP confirmation<br>accept joining member request,<br>sending group profile, etc.<br>establish group member connection
|
||||
A ->> B: x.grp.link.mem<br>send inviting member profile
|
||||
end
|
||||
|
||||
note over M, B: 2. introduce new member Bob to all existing members
|
||||
A ->> M: x.grp.mem.new<br>"announce" Bob<br>to existing members<br>(via member connections)
|
||||
@@ -20,14 +27,25 @@ sequenceDiagram
|
||||
end
|
||||
A ->> M: x.grp.mem.fwd<br>forward "invitations" and<br>Bob's chat protocol version<br>to all members<br>(via member connections)
|
||||
|
||||
note over M, B: group message forwarding<br>(while connections between members are being established)
|
||||
M -->> B: messages between members and Bob are forwarded by Alice
|
||||
B -->> M:
|
||||
|
||||
note over M, B: 3. establish direct and group member connections
|
||||
M ->> B: establish group member connection
|
||||
|
||||
opt chat protocol compatible version < 2
|
||||
M ->> B: establish direct connection
|
||||
note over M, C: 4. deduplicate new contact
|
||||
note over M, C: 3*. deduplicate new contact
|
||||
B ->> M: x.info.probe<br>"probe" is sent to all new members
|
||||
B ->> C: x.info.probe.check<br>"probe" hash,<br>in case contact and<br>member profiles match
|
||||
C ->> B: x.info.probe.ok<br> original "probe",<br> in case contact and member<br>are the same user
|
||||
note over B: merge existing and new contacts if received and sent probe hashes match
|
||||
end
|
||||
|
||||
note over M, B: 4. notify inviting member that connection is established
|
||||
M ->> A: x.grp.mem.con
|
||||
B ->> A: x.grp.mem.con
|
||||
note over A: stops forwarding messages
|
||||
M -->> B: messages are sent via group connection without forwarding
|
||||
B -->> M:
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 41 KiB |
@@ -2,7 +2,7 @@
|
||||
title: SimpleX Chat Protocol
|
||||
revision: 08.08.2022
|
||||
---
|
||||
DRAFT Revision 0.1, 2022-08-08
|
||||
Revision 2, 2024-06-24
|
||||
|
||||
Evgeny Poberezkin
|
||||
|
||||
@@ -17,18 +17,23 @@ SimpleX Chat Protocol is a protocol used by SimpleX Chat clients to exchange mes
|
||||
The scope of SimpleX Chat Protocol is application level messages, both for chat functionality, related to the conversations between the clients, and extensible for any other application functions. Currently supported chat functions:
|
||||
|
||||
- direct and group messages,
|
||||
- message replies (quoting), forwarded messages and message deletions,
|
||||
- message attachments: images and files,
|
||||
- message replies (quoting), message editing, forwarded messages and message deletions,
|
||||
- message attachments: images, videos, voice messages and files,
|
||||
- creating and managing chat groups,
|
||||
- invitation and signalling for audio/video WebRTC calls.
|
||||
|
||||
## General message format
|
||||
|
||||
SimpleX Chat protocol supports two message formats:
|
||||
SimpleX Chat protocol supports these message formats:
|
||||
|
||||
- JSON-based format for chat and application messages.
|
||||
- compressed format for adapting larger messages to reduced size of message envelope, caused by addition of PQ encryption keys to SMP agent message envelope.
|
||||
- binary format for sending files or any other binary data.
|
||||
|
||||
JSON-based message format supports batching inside a single container message, by encoding list of messages as JSON array.
|
||||
|
||||
Current implementation of chat protocol in SimpleX Chat uses SimpleX File Transfer Protocol (XFTP) for file transfer, with passing file description as chat protocol messages, instead passing files in binary format via SMP connections.
|
||||
|
||||
### JSON format for chat and application messages
|
||||
|
||||
This document uses JTD schemas [RFC 8927](https://www.rfc-editor.org/rfc/rfc8927.html) to define the properties of chat messages, with some additional restrictions on message properties included in metadata member of JTD schemas. In case of any contradiction between JSON examples and JTD schema the latter MUST be considered correct.
|
||||
@@ -77,8 +82,22 @@ For example, this message defines a simple text message `"hello!"`:
|
||||
|
||||
`params` property includes message data, depending on `event`, as defined below and in [JTD schema](./simplex-chat.schema.json).
|
||||
|
||||
### Compressed format
|
||||
|
||||
The syntax of compressed message is defined by the following ABNF notation:
|
||||
|
||||
```abnf
|
||||
compressedMessage = %s"X" 1*15780 OCTET; compressed message data
|
||||
```
|
||||
|
||||
Compressed message is required to fit into 13388 bytes, accounting for agent overhead (see Protocol's maxCompressedMsgLength).
|
||||
|
||||
The actual JSON message is required to fit into 15610 bytes, accounting for group message forwarding (x.grp.msg.forward) overhead (see Protocol's maxEncodedMsgLength).
|
||||
|
||||
### Binary format for sending files
|
||||
|
||||
> Note: Planned to be deprecated. No longer used for file transfer in SimpleX Chat implementation of chat protocol.
|
||||
|
||||
SimpleX Chat clients use separate connections to send files using a binary format. File chunk size send in each message MUST NOT be bigger than 15,780 bytes to fit into 16kb (16384 bytes) transport block.
|
||||
|
||||
The syntax of each message used to send files is defined by the following ABNF notation:
|
||||
@@ -117,7 +136,9 @@ SimpleX Chat Protocol supports the following message types passed in `event` pro
|
||||
- `x.contact` - contact profile and additional data sent as part of contact request to a long-term contact address.
|
||||
- `x.info*` - messages to send, update and de-duplicate contact profiles.
|
||||
- `x.msg.*` - messages to create, update and delete content chat items.
|
||||
- `x.msg.file.descr` - message to transfer XFTP file description.
|
||||
- `x.file.*` - messages to accept and cancel sending files (see files sub-protocol).
|
||||
- `x.direct.del` - message to notify about contact deletion.
|
||||
- `x.grp.*` - messages used to manage groups and group members (see group sub-protocol).
|
||||
- `x.call.*` - messages to invite to WebRTC calls and send signalling messages.
|
||||
- `x.ok` - message sent during connection handshake.
|
||||
@@ -136,7 +157,7 @@ This message is sent by both sides of the connection during the connection hands
|
||||
|
||||
### Probing for duplicate contacts
|
||||
|
||||
As there are no globally unique user identitifiers, when the contact a user is already connected to is added to the group by some other group member, this contact will be added to user's list of contacts as a new contact. To allow merging such contacts, "a probe" (random base64url-encoded 32 bytes) SHOULD be sent to all new members as part of `x.info.probe` message and, in case there is a contact with the same profile, the hash of the probe MAY be sent to it as part of `x.info.probe.check` message. In case both the new member and the existing contact are the same user (they would receive both the probe and its hash), the contact would send back the original probe as part of `x.info.probe.ok` message via the previously existing contact connection – proving to the sender that this new member and the existing contact are the same user, in which case the sender SHOULD merge these two contacts.
|
||||
As there are no globally unique user identifiers, when the contact a user is already connected to is added to the group by some other group member, this contact will be added to user's list of contacts as a new contact. To allow merging such contacts, "a probe" (random base64url-encoded 32 bytes) SHOULD be sent to all new members as part of `x.info.probe` message and, in case there is a contact with the same profile, the hash of the probe MAY be sent to it as part of `x.info.probe.check` message. In case both the new member and the existing contact are the same user (they would receive both the probe and its hash), the contact would send back the original probe as part of `x.info.probe.ok` message via the previously existing contact connection – proving to the sender that this new member and the existing contact are the same user, in which case the sender SHOULD merge these two contacts.
|
||||
|
||||
Sending clients MAY disable this functionality, and receiving clients MAY ignore probe messages.
|
||||
|
||||
@@ -155,6 +176,8 @@ Message content can be one of four types:
|
||||
- `text` - no file attachment is expected for this format, `text` property MUST be non-empty.
|
||||
- `file` - attached file is required, `text` property MAY be empty.
|
||||
- `image` - attached file is required, `text` property MAY be empty.
|
||||
- `video` - attached file is required, `text` property MAY be empty.
|
||||
- `voice` - attached file is required, `text` property MAY be empty.
|
||||
- `link` - no file attachment is expected, `text` property MUST be non-empty. `preview` property contains information about link preview.
|
||||
|
||||
See `/definition/msgContent` in [JTD schema](./simplex-chat.schema.json) for message container format.
|
||||
@@ -181,25 +204,29 @@ File attachment can optionally include connection address to receive the file -
|
||||
|
||||
`x.file.cancel` message is sent to notify the recipient that sending of the file was cancelled. It is sent in response to accepting the file with `x.file.acpt.inv` message. It is sent in the same connection where the file was offered.
|
||||
|
||||
`x.msg.file.descr` message is used to send XFTP file description. File descriptions that don't fit into a single chat protocol message are sent in parts, with messages including part number (`fileDescrPartNo`) and description completion marker (`fileDescrComplete`). Recipient client accumulates description parts and starts file download upon completing file description.
|
||||
|
||||
## Sub-protocol for chat groups
|
||||
|
||||
### Decentralized design for chat groups
|
||||
|
||||
SimpleX Chat groups are fully decentralized and do not have any globally unique group identifiers - they are only defined on client devices as a group profile and a set of bi-directional SimpleX connections with other group members. When a new member accepts group invitation, the inviting member introduces a new member to all existing members and forwards the connection addresses so that they can establish direct and group member connections.
|
||||
SimpleX Chat groups are fully decentralized and do not have any globally unique group identifiers - they are only defined on client devices as a group profile and a set of bi-directional SimpleX connections with other group members. When a new member accepts group invitation or joins via group link, the inviting member introduces a new member to all existing members and forwards the connection addresses so that they can establish direct and group member connections.
|
||||
|
||||
There is a possibility of the attack here: as the introducing member forwards the addresses, they can substitute them with other addresses, performing MITM attack on the communication between existing and introduced members - this is similar to the communication operator being able to perform MITM on any connection between the users. To mitigate this attack this group sub-protocol will be extended to allow validating security of the connection by sending connection verification out-of-band.
|
||||
|
||||
Clients are RECOMMENDED to indicate in the UI whether the connection to a group member or contact was made directly or via annother user.
|
||||
Clients are RECOMMENDED to indicate in the UI whether the connection to a group member or contact was made directly or via another user.
|
||||
|
||||
Each member in the group is identified by a group-wide unique identifier used by all members in the group. This is to allow referencing members in the messages and to allow group message integrity validation.
|
||||
|
||||
The diagram below shows the sequence of messages sent between the users' clients to add the new member to the group.
|
||||
|
||||
While introduced members establish connection inside group, inviting member forwards messages between them by sending `x.grp.msg.forward` messages. When introduced members finalize connection, they notify inviting member to stop forwarding via `x.grp.mem.con` message.
|
||||
|
||||

|
||||
|
||||
### Member roles
|
||||
|
||||
Currently members can have one of three roles - `owner`, `admin` and `member`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role.
|
||||
Currently members can have one of three roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages.
|
||||
|
||||
### Messages to manage groups and add members
|
||||
|
||||
@@ -207,6 +234,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.acpt` message is sent as part of group member connection handshake, only to the inviting user.
|
||||
|
||||
`x.grp.link.inv` message is sent as part of connection handshake to member joining via group link, and contains group profile and initial information about inviting and joining member.
|
||||
|
||||
`x.grp.link.mem` message is sent as part of connection handshake to member joining via group link, and contains remaining information about inviting member.
|
||||
|
||||
`x.grp.mem.new` message is sent by the inviting user to all connected members (and scheduled as pending to all announced but not yet connected members) to announce a new member to the existing members. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role.
|
||||
|
||||
`x.grp.mem.intro` messages are sent by the inviting user to the invited member, via their group member connection, one message for each existing member. When this message is sent by any other member than the one who invited the recipient it MUST be ignored.
|
||||
@@ -219,6 +250,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.mem.role` message is sent to update group member role - it is sent to all members by the member who updated the role of the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with role less than `admin`.
|
||||
|
||||
`x.grp.mem.restrict` message is sent to group members to communicate group member restrictions, such as member being blocked for sending messages.
|
||||
|
||||
`x.grp.mem.con` message is sent by members connecting inside group to inviting member, to notify the inviting member they have completed the connection and no longer require forwarding messages between them.
|
||||
|
||||
`x.grp.mem.del` message is sent to delete a member - it is sent to all members by the member who deletes the member referenced in this message. This message MUST only be sent by members with `admin` or `owner` role. Receiving clients MUST ignore this message if it is received from member with `member` role.
|
||||
|
||||
`x.grp.leave` message is sent to all members by the member leaving the group. If the only group `owner` leaves the group, it will not be possible to delete it with `x.grp.del` message - but all members can still leave the group with `x.grp.leave` message and then delete a local copy of the group.
|
||||
@@ -227,6 +262,10 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
||||
|
||||
`x.grp.info` message is sent to all members by the member who updated group profile. Only group owners can update group profiles. Clients MAY implement some conflict resolution strategy - it is currently not implemented by SimpleX Chat client. This message MUST only be sent by members with `owner` role. Receiving clients MUST ignore this message if it is received from member other than with `owner` role.
|
||||
|
||||
`x.grp.direct.inv` message is sent to a group member to propose establishing a direct connection between members, thus creating a contact with another member.
|
||||
|
||||
`x.grp.msg.forward` message is sent by inviting member to forward messages between introduced members, while they are connecting.
|
||||
|
||||
## Sub-protocol for WebRTC audio/video calls
|
||||
|
||||
This sub-protocol is used to send call invitations and to negotiate end-to-end encryption keys and pass WebRTC signalling information.
|
||||
@@ -240,3 +279,66 @@ These message are used for WebRTC calls:
|
||||
3. `x.call.answer`: to continue with call connection the initiating clients must reply with `x.call.answer` message. This message contains WebRTC answer and collected ICE candidates. Additional ICE candidates can be sent in `x.call.extra` message.
|
||||
|
||||
4. `x.call.end` message is sent to notify the other party that the call is terminated.
|
||||
|
||||
## Threat model
|
||||
|
||||
This threat model compliments SMP, XFTP, push notifications and XRCP protocols threat models:
|
||||
|
||||
- [SimpleX Messaging Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md#threat-model);
|
||||
- [SimpleX File Transfer Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xftp.md#threat-model);
|
||||
- [Push notifications threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/push-notifications.md#threat-model);
|
||||
- [SimpleX Remote Control Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xrcp.md#threat-model).
|
||||
|
||||
#### A user's contact
|
||||
|
||||
*can:*
|
||||
|
||||
- send messages prohibited by user's preferences or otherwise act non-compliantly with user's preferences (for example, if message with updated preferences was lost or failed to be processed, or with modified client), in which case user client should treat such messages and actions as prohibited.
|
||||
|
||||
- by exchanging special messages with user's client, match user's contact with existing group members and/or contacts that have identical user profile (see [Probing for duplicate contacts](#probing-for-duplicate-contacts)).
|
||||
|
||||
- identify that and when a user is using SimpleX, in case user has delivery receipts enabled, or based on other automated client responses.
|
||||
|
||||
*cannot:*
|
||||
|
||||
- match user's contact with existing group members and/or contacts with different or with incognito profiles.
|
||||
|
||||
- match user's contact without communicating with the user's client.
|
||||
|
||||
#### A group member
|
||||
|
||||
*can:*
|
||||
|
||||
- send messages prohibited by group's preferences and member restrictions or otherwise act non-compliantly with preferences and restrictions (for example, if decentralized group state diverged, or with modified client), in which case user client should treat such messages and actions as prohibited.
|
||||
|
||||
- create a direct contact with a user if group permissions allow it.
|
||||
|
||||
- by exchanging special messages with user's client, match user's group member record with the existing group members and/or contacts that have identical user profile.
|
||||
|
||||
- undetectably send different messages to different group members, or selectively send messages to some members and not send to others.
|
||||
|
||||
- identify that and when a user is using SimpleX, in case user has delivery receipts enabled, or based on other automated client responses.
|
||||
|
||||
- join the same group several times, from the same or from different user profile, and pretend to be different members.
|
||||
|
||||
*cannot:*
|
||||
|
||||
- match user's contact with existing group members and/or contacts with different or with incognito profiles.
|
||||
|
||||
- match user's group member record with existing group members and/or contacts without communication of user's client.
|
||||
|
||||
- determine whether two group members with different or with incognito profiles are the same user.
|
||||
|
||||
#### A group admin
|
||||
|
||||
*can:*
|
||||
|
||||
- carry out MITM attack between user and other group member(s) when forwarding invitations for group connections (user can detect such attack by verifying connection security codes out-of-band).
|
||||
|
||||
- undetectably forward different messages to different group members, selectively adding, modifying, and dropping forwarded messages.
|
||||
|
||||
- disrupt decentralized group state by sending different messages that change group state (such as adding or removing members, member role changes, etc.) to different group members, or sending such messages selectively.
|
||||
|
||||
*cannot:*
|
||||
|
||||
- prove that two group members with incognito profiles is the same user.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "non-empty string without spaces, the first character must not be # or @"
|
||||
"format": "non-empty string, the first character must not be # or @"
|
||||
}
|
||||
},
|
||||
"fullName": {"type": "string"}
|
||||
@@ -19,6 +19,39 @@
|
||||
"metadata": {
|
||||
"format": "data URI format for base64 encoded image"
|
||||
}
|
||||
},
|
||||
"contactLink": {"ref": "connReqUri"},
|
||||
"preferences": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "JSON encoded user preferences"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"groupProfile": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "non-empty string, the first character must not be # or @"
|
||||
}
|
||||
},
|
||||
"fullName": {"type": "string"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "data URI format for base64 encoded image"
|
||||
}
|
||||
},
|
||||
"groupPreferences": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "JSON encoded user preferences"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
@@ -29,6 +62,8 @@
|
||||
},
|
||||
"optionalProperties": {
|
||||
"file": {"ref": "fileInvitation"},
|
||||
"ttl": {"type": "integer"},
|
||||
"live": {"type": "boolean"},
|
||||
"quote": {
|
||||
"properties": {
|
||||
"msgRef": {"ref": "msgRef"},
|
||||
@@ -56,17 +91,47 @@
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}},
|
||||
"image": {"ref": "base64url"}
|
||||
"properties": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}},
|
||||
"image": {"ref": "base64url"}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"properties": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}},
|
||||
"image": {"ref": "base64url"},
|
||||
"duration": {"type": "integer"}
|
||||
}
|
||||
},
|
||||
"voice": {
|
||||
"properties": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}},
|
||||
"duration": {"type": "integer"}
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}}
|
||||
"properties": {
|
||||
"text": {"type": "string", "metadata": {"comment": "can be empty"}}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"comment": "it is RECOMMENDED that the clients support other values in `type` properties showing them as text messages in case `text` property is present"
|
||||
}
|
||||
},
|
||||
"msgReaction" : {
|
||||
"discriminator": "type",
|
||||
"mapping": {
|
||||
"emoji": {
|
||||
"properties": {
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"metadata": {"comment": "emoji character"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"msgRef": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
@@ -91,7 +156,31 @@
|
||||
"fileSize": {"type": "uint32"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"fileConnReq": {"ref": "connReqUri"}
|
||||
"fileDigest": {"ref": "base64url"},
|
||||
"fileConnReq": {"ref": "connReqUri"},
|
||||
"fileDescr": {"ref": "fileDescription"}
|
||||
}
|
||||
},
|
||||
"fileDescription": {
|
||||
"properties": {
|
||||
"fileDescrText": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "XFTP file description part text"
|
||||
}
|
||||
},
|
||||
"fileDescrPartNo": {
|
||||
"type": "integer",
|
||||
"metadata": {
|
||||
"format": "XFTP file description part number"
|
||||
}
|
||||
},
|
||||
"fileDescrComplete": {
|
||||
"type": "boolean",
|
||||
"metadata": {
|
||||
"format": "XFTP file description completion marker"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"linkPreview": {
|
||||
@@ -100,6 +189,21 @@
|
||||
"title": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"image": {"ref": "base64url"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"content": {"ref": "linkContent"}
|
||||
}
|
||||
},
|
||||
"linkContent": {
|
||||
"discriminator": "type",
|
||||
"mapping": {
|
||||
"page": {},
|
||||
"image": {},
|
||||
"video": {
|
||||
"optionalProperties": {
|
||||
"duration": {"type": "integer"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"groupInvitation": {
|
||||
@@ -107,15 +211,27 @@
|
||||
"fromMember": {"ref": "memberIdRole"},
|
||||
"invitedMember": {"ref": "memberIdRole"},
|
||||
"connRequest": {"ref": "connReqUri"},
|
||||
"groupProfile": {"ref": "profile"}
|
||||
"groupProfile": {"ref": "groupProfile"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"groupLinkId": {"ref": "base64url"},
|
||||
"groupSize": {"type": "integer"},
|
||||
"metadata": {
|
||||
"comment": "used to identify invitation via group link"
|
||||
"comment": "groupLinkId is used to identify invitation via group link"
|
||||
}
|
||||
}
|
||||
},
|
||||
"groupLinkInvitation": {
|
||||
"properties": {
|
||||
"fromMember": {"ref": "memberIdRole"},
|
||||
"fromMemberName": {"type": "string"},
|
||||
"invitedMember": {"ref": "memberIdRole"},
|
||||
"groupProfile": {"ref": "groupProfile"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"groupSize": {"type": "integer"}
|
||||
}
|
||||
},
|
||||
"memberIdRole": {
|
||||
"properties": {
|
||||
"memberId": {"ref": "base64url"},
|
||||
@@ -127,16 +243,35 @@
|
||||
"memberId": {"ref": "base64url"},
|
||||
"memberRole": {"ref": "groupMemberRole"},
|
||||
"profile": {"ref": "profile"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"v": {"ref": "chatVersionRange"}
|
||||
}
|
||||
},
|
||||
"memberRestrictions": {
|
||||
"properties": {
|
||||
"restriction": {"ref": "memberRestrictionStatus"}
|
||||
}
|
||||
},
|
||||
"memberRestrictionStatus": {
|
||||
"enum": ["blocked", "unrestricted"]
|
||||
},
|
||||
"chatVersionRange": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "chat version range string encoded as `<min>-<max>`, or as `<number>` if min = max"
|
||||
}
|
||||
},
|
||||
"introInvitation": {
|
||||
"properties": {
|
||||
"groupConnReq": {"ref": "connReqUri"},
|
||||
"groupConnReq": {"ref": "connReqUri"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"directConnReq": {"ref": "connReqUri"}
|
||||
}
|
||||
},
|
||||
"groupMemberRole": {
|
||||
"enum": ["author", "member", "admin", "owner"]
|
||||
"enum": ["observer", "author", "member", "admin", "owner"]
|
||||
},
|
||||
"callInvitation": {
|
||||
"properties": {
|
||||
@@ -257,6 +392,17 @@
|
||||
"params": {"ref": "msgContainer"}
|
||||
}
|
||||
},
|
||||
"x.msg.file.descr": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"fileDescr": {"ref": "fileDescription"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.msg.update": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
@@ -264,6 +410,10 @@
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"content": {"ref": "msgContent"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"ttl": {"type": "integer"},
|
||||
"live": {"type": "boolean"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,6 +424,24 @@
|
||||
"params": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"memberId": {"ref": "base64url"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.msg.react": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"reaction": {"ref": "msgReaction"},
|
||||
"add": {"type": "boolean"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"memberId": {"ref": "base64url"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,8 +462,10 @@
|
||||
"params": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"fileConnReq": {"ref": "connReqUri"},
|
||||
"fileName": {"type": "string"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"fileConnReq": {"ref": "connReqUri"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +480,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.direct.del": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.inv": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
@@ -330,6 +508,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.link.inv": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"groupLinkInvitation": {"ref": "groupLinkInvitation"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.link.mem": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"profile": {"ref": "profile"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.mem.new": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
@@ -346,6 +544,9 @@
|
||||
"params": {
|
||||
"properties": {
|
||||
"memberInfo": {"ref": "memberInfo"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"memberRestrictions": {"ref": "memberRestrictions"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,6 +595,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.mem.restrict": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"memberId": {"ref": "base64url"},
|
||||
"memberRestrictions": {"ref": "memberRestrictions"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.mem.con": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"memberId": {"ref": "base64url"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.mem.del": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
@@ -425,7 +647,42 @@
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"groupProfile": {"ref": "profile"}
|
||||
"groupProfile": {"ref": "groupProfile"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.direct.inv": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"connReq": {"ref": "connReqUri"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"content": {"ref": "msgContent"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x.grp.msg.forward": {
|
||||
"properties": {
|
||||
"msgId": {"ref": "base64url"},
|
||||
"params": {
|
||||
"properties": {
|
||||
"memberId": {"ref": "base64url"},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "JSON encoded chat message"
|
||||
}
|
||||
},
|
||||
"msgTs": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"format": "ISO8601 UTC time of the message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,7 +693,7 @@
|
||||
"params": {
|
||||
"properties": {
|
||||
"callId": {"ref": "base64url"},
|
||||
"invitation": {}
|
||||
"invitation": {"ref": "callInvitation"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Agent stats persistence
|
||||
|
||||
## Problem
|
||||
|
||||
State/state tracked in agent are lost on app restart, which makes it difficult to debug user bugs.
|
||||
|
||||
## Solution
|
||||
|
||||
Persist stats between sessions.
|
||||
|
||||
App terminal signals may vary per platform / be absent (?) -> persist stats periodically.
|
||||
|
||||
Stats would have `<userId, server>` key, so we don't want to store them in a plaintext file to not leak used servers locally -> persist in encrypted db.
|
||||
|
||||
There's couple of orthogonal design decision to be made:
|
||||
- persist in chat or in agent db
|
||||
- pros for chat:
|
||||
- possibly less contention for db than agent
|
||||
- pros for agent:
|
||||
- no unnecessary back and forth, especially if agent starts accumulating from past sessions and has to be parameterized with past stats (see below)
|
||||
- agent to start accumulating from past sessions stats, or keep past separately and only accumulate for current session from zeros
|
||||
- pros for accumulating from past sessions:
|
||||
- easier to maintain stats - e.g. user deletion has to remove keys, which is more convoluted if past stats are not stored in memory
|
||||
- simpler UI - overall stats, no differentiation for past/current session (or less logic in backend preparing presentation data)
|
||||
- pros for accumulating from zeros:
|
||||
- simpler start logic - no need to restore stats from agent db / pass initial stats from chat db
|
||||
- can differentiate between past sessions and current session stats in UI
|
||||
|
||||
### Option 1 - Persist in chat db, agent to track only current session
|
||||
|
||||
- Chat stores stats in such table:
|
||||
|
||||
```sql
|
||||
CREATE TABLE agent_stats(
|
||||
agent_stats_id INTEGER PRIMARY KEY, -- dummy id, there will only be one record
|
||||
past_stats TEXT, -- accumulated from previous sessions
|
||||
session_stats TEXT, -- current session
|
||||
past_started_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of tracking stats, reset on stats reset
|
||||
session_started_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of current session
|
||||
session_updated_at TEXT NOT NULL DEFAULT(datetime('now')) -- last update of current session stats (periodic, frequent updates)
|
||||
);
|
||||
```
|
||||
|
||||
- Chat periodically calls getAgentServersStats api and updates `session_stats`.
|
||||
- interval? should be short to not lose too much data, 5-30 seconds?
|
||||
- On start `session_stats` are accumulated into `past_stats` and set to null.
|
||||
- On user deletion, agent updates current session stats in memory (removes keys), chat has to do same for both stats fields in db.
|
||||
- other cases where stats have to be manipulated in similar way?
|
||||
|
||||
### Option 2 - Persist in chat db, agent to accumulate stats from past sessions
|
||||
|
||||
- Table is only used for persistence of overall stats:
|
||||
|
||||
```sql
|
||||
CREATE TABLE agent_stats(
|
||||
agent_stats_id INTEGER PRIMARY KEY, -- dummy id, there will only be one record
|
||||
agent_stats TEXT, -- overall stats - past and session
|
||||
started_tracking_at TEXT NOT NULL DEFAULT(datetime('now')), -- starting point of tracking stats, reset on stats reset
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
- Chat to parameterize creation of agent client with initial stats.
|
||||
|
||||
### Option 3 - Persist in agent db, agent to differentiate past stats and session stats
|
||||
|
||||
- Table in agent db similar to option 1.
|
||||
- Agent is responsible for periodic updates in session, as well as accumulating into "past" and resetting session stats on start.
|
||||
- Agent only communicates stats to chat on request.
|
||||
- On user deletion agent is fully responsible for maintaining both in-memory session stats, and updating db records.
|
||||
|
||||
### Option 4 - Persist in agent db, agent to accumulate stats from past sessions
|
||||
|
||||
- Table in agent db similar to option 2.
|
||||
- On start agent restores initial stats into memory by itself.
|
||||
- Since all stats are in memory, on user deletion it's enough to update in memory without updating db.
|
||||
- there is a race possible where agent crashes after updating stats (removing user keys) in memory before database stats have been overwritten by a periodic update, so it may be better to immediately overwrite and not wait for periodic update.
|
||||
- still at least there's at least no additional logic to update past stats.
|
||||
|
||||
### Other considerations
|
||||
|
||||
Why is it important to timely remove user keys from past stats?
|
||||
- stats not being saved for past users:
|
||||
- important both privacy-wise and to not cause confusion when showing "All" stats (e.g. user summing up across users stats would have smaller total than total stats).
|
||||
- to avoid accidentally mixing up with newer users.
|
||||
- though we do have an AUTOINCREMENT user_id in agent so probably it wouldn't be a problem.
|
||||
- on the other hand maybe we don't want to "forget" stats on user deletion so that stats would reflect networking more accurately?
|
||||
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 (
|
||||
|
||||
@@ -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 -o /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,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."8a3b72458f917e9867f4e3640dda0fa1827ff6cf" = "1mmxdaj563kjmlkacxdnq62n6mzw9khampzaqghnk6iiwzdig0qy";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."c7886926870e97fa592d51fa36a2cdec49296388" = "1r3nibcgw3whl0q3ssyr1606x4ilqphhzqyihi3aw4nw5fmz226h";
|
||||
"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";
|
||||
|
||||
@@ -160,6 +160,7 @@ library
|
||||
Simplex.Chat.Remote.RevHTTP
|
||||
Simplex.Chat.Remote.Transport
|
||||
Simplex.Chat.Remote.Types
|
||||
Simplex.Chat.Stats
|
||||
Simplex.Chat.Store
|
||||
Simplex.Chat.Store.AppSettings
|
||||
Simplex.Chat.Store.Connections
|
||||
|
||||
+21
-5
@@ -20,6 +20,7 @@ import Control.Applicative (optional, (<|>))
|
||||
import Control.Concurrent.STM (retry)
|
||||
import Control.Logger.Simple
|
||||
import Control.Monad
|
||||
import Simplex.Chat.Stats
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.IO.Unlift
|
||||
import Control.Monad.Reader
|
||||
@@ -84,7 +85,6 @@ import Simplex.Chat.Store.Shared
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Chat.Util (encryptFile, liftIOEither, shuffle)
|
||||
import qualified Simplex.Chat.Util as U
|
||||
import Simplex.FileTransfer.Client.Main (maxFileSize, maxFileSizeHard)
|
||||
@@ -113,7 +113,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (base64P)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol, ProtocolServer)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
@@ -369,7 +369,7 @@ activeAgentServers ChatConfig {defaultServers} p =
|
||||
fromMaybe (cfgServers p defaultServers)
|
||||
. nonEmpty
|
||||
. map (\ServerCfg {server} -> server)
|
||||
. filter (\ServerCfg {enabled} -> enabled)
|
||||
. filter (\ServerCfg {enabled} -> enabled == SEEnabled)
|
||||
|
||||
cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ProtoServerWithAuth p))
|
||||
cfgServers p DefaultAgentServers {smp, xftp} = case p of
|
||||
@@ -1315,7 +1315,7 @@ processChatCommand' vr = \case
|
||||
servers' = fromMaybe (L.map toServerCfg defServers) $ nonEmpty servers
|
||||
pure $ CRUserProtoServers user $ AUPS $ UserProtoServers p servers' defServers
|
||||
where
|
||||
toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = True}
|
||||
toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = SEEnabled}
|
||||
GetUserProtoServers aProtocol -> withUser $ \User {userId} ->
|
||||
processChatCommand $ APIGetUserProtoServers userId aProtocol
|
||||
APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) -> withUserId userId $ \user -> withServerProtocol p $ do
|
||||
@@ -2253,6 +2253,21 @@ processChatCommand' vr = \case
|
||||
CLUserContact ucId -> "UserContact " <> show ucId
|
||||
CLFile fId -> "File " <> show fId
|
||||
DebugEvent event -> toView event >> ok_
|
||||
GetAgentServersSummary userId -> withUserId userId $ \user -> do
|
||||
agentServersSummary <- lift $ withAgent' getAgentServersSummary
|
||||
users <- withStore' getUsers
|
||||
smpServers <- getUserServers user SPSMP
|
||||
xftpServers <- getUserServers user SPXFTP
|
||||
let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers
|
||||
pure $ CRAgentServersSummary user presentedServersSummary
|
||||
where
|
||||
getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM [ProtocolServer p]
|
||||
getUserServers users protocol = do
|
||||
ChatConfig {defaultServers} <- asks config
|
||||
let defServers = cfgServers protocol defaultServers
|
||||
servers <- map (\ServerCfg {server} -> server) <$> withStore' (`getProtocolServers` users)
|
||||
let srvs = if null servers then L.toList defServers else servers
|
||||
pure $ map protoServer srvs
|
||||
GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary
|
||||
GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails
|
||||
GetAgentStats -> lift $ CRAgentStats . map stat <$> withAgent' getAgentStats
|
||||
@@ -7611,6 +7626,7 @@ chatCommandP =
|
||||
("/version" <|> "/v") $> ShowVersion,
|
||||
"/debug locks" $> DebugLocks,
|
||||
"/debug event " *> (DebugEvent <$> jsonP),
|
||||
"/get servers summary " *> (GetAgentServersSummary <$> A.decimal),
|
||||
"/get stats" $> GetAgentStats,
|
||||
"/reset stats" $> ResetAgentStats,
|
||||
"/get subs" $> GetAgentSubs,
|
||||
@@ -7755,7 +7771,7 @@ chatCommandP =
|
||||
(Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP)))
|
||||
(pure Nothing)
|
||||
srvCfgP = strP >>= \case AProtocolType p -> APSC p <$> (A.space *> jsonP)
|
||||
toServerCfg server = ServerCfg {server, preset = False, tested = Nothing, enabled = True}
|
||||
toServerCfg server = ServerCfg {server, preset = False, tested = Nothing, enabled = SEEnabled}
|
||||
rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P))
|
||||
text1P = safeDecodeUtf8 <$> A.takeTill (== ' ')
|
||||
char_ = optional . A.char
|
||||
|
||||
@@ -21,10 +21,10 @@ import Data.Time.Clock (UTCTime)
|
||||
import Database.SQLite.Simple.FromField (FromField (..))
|
||||
import Database.SQLite.Simple.ToField (ToField (..))
|
||||
import Simplex.Chat.Types (Contact, ContactId, User)
|
||||
import Simplex.Chat.Types.Util (decodeJSON, encodeJSON)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON)
|
||||
|
||||
data Call = Call
|
||||
{ contactId :: ContactId,
|
||||
|
||||
@@ -60,6 +60,7 @@ import Simplex.Chat.Messages.CIContent
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Remote.AppVersion
|
||||
import Simplex.Chat.Remote.Types
|
||||
import Simplex.Chat.Stats (PresentedServersSummary)
|
||||
import Simplex.Chat.Store (AutoAccept, ChatLockEntity, StoreError (..), UserContactLink, UserMsgReceiptSettings)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
@@ -75,7 +76,7 @@ import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction)
|
||||
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||
import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback (..))
|
||||
import Simplex.Messaging.Client (SMPProxyFallback (..), SMPProxyMode (..))
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
@@ -505,6 +506,7 @@ data ChatCommand
|
||||
| ShowVersion
|
||||
| DebugLocks
|
||||
| DebugEvent ChatResponse
|
||||
| GetAgentServersSummary UserId
|
||||
| GetAgentStats
|
||||
| ResetAgentStats
|
||||
| GetAgentSubs
|
||||
@@ -756,6 +758,7 @@ data ChatResponse
|
||||
| CRSQLResult {rows :: [Text]}
|
||||
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
|
||||
| CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks}
|
||||
| CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary}
|
||||
| CRAgentStats {agentStats :: [[String]]}
|
||||
| CRAgentWorkersDetails {agentWorkersDetails :: AgentWorkersDetails}
|
||||
| CRAgentWorkersSummary {agentWorkersSummary :: AgentWorkersSummary}
|
||||
|
||||
@@ -29,13 +29,12 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..))
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (ProtocolServer (..))
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||
import Simplex.Messaging.Util (decodeJSON, safeDecodeUtf8)
|
||||
import System.Console.ANSI.Types
|
||||
import qualified Text.Email.Validate as Email
|
||||
|
||||
@@ -146,7 +145,7 @@ parseMarkdown s = fromRight (unmarked s) $ A.parseOnly (markdownP <* A.endOfInpu
|
||||
|
||||
isSimplexLink :: Format -> Bool
|
||||
isSimplexLink = \case
|
||||
SimplexLink {} -> True;
|
||||
SimplexLink {} -> True
|
||||
_ -> False
|
||||
|
||||
markdownP :: Parser Markdown
|
||||
|
||||
@@ -29,12 +29,11 @@ import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..))
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOn, pattern PQEncOff)
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOff, pattern PQEncOn)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow, (<$?>))
|
||||
import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>))
|
||||
|
||||
data MsgDirection = MDRcv | MDSnd
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -46,14 +46,13 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
|
||||
import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
||||
import Simplex.Messaging.Protocol (MsgBody)
|
||||
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||
import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>))
|
||||
import Simplex.Messaging.Version hiding (version)
|
||||
|
||||
-- Chat version history:
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||
|
||||
module Simplex.Chat.Stats where
|
||||
|
||||
import Control.Applicative ((<|>))
|
||||
import qualified Data.Aeson.TH as J
|
||||
import Data.Map.Strict (Map)
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.Maybe (fromMaybe, isJust)
|
||||
import Data.Time.Clock (UTCTime)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Messaging.Agent.Client
|
||||
import Simplex.Messaging.Agent.Protocol (UserId)
|
||||
import Simplex.Messaging.Agent.Stats
|
||||
import Simplex.Messaging.Parsers (defaultJSON)
|
||||
import Simplex.Messaging.Protocol
|
||||
|
||||
data PresentedServersSummary = PresentedServersSummary
|
||||
{ statsStartedAt :: UTCTime,
|
||||
currentUserServers :: ServersSummary,
|
||||
allUsersServers :: ServersSummary
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
-- Presentation of servers will be split into separate categories,
|
||||
-- so users can differentiate currently used (connected) servers,
|
||||
-- previously connected servers that were in use in previous sessions,
|
||||
-- and servers that are only proxied (not connected directly).
|
||||
data ServersSummary = ServersSummary
|
||||
{ -- currently used SMP servers are those with Just in sessions and/or subs in SMPServerSummary;
|
||||
-- all other servers would fall either into previously used or only proxied servers category
|
||||
currentlyUsedSMPServers :: [SMPServerSummary],
|
||||
-- previously used SMP servers are those with Nothing in sessions and subs,
|
||||
-- and have any of sentDirect, sentProxied, recvMsgs, etc. > 0 in server stats (see toPresentedServersSummary);
|
||||
-- remaining servers would fall into only proxied servers category
|
||||
previouslyUsedSMPServers :: [SMPServerSummary],
|
||||
-- only proxied SMP servers are those that aren't (according to current state - sessions and subs)
|
||||
-- and weren't (according to stats) connected directly; they would have Nothing in sessions and subs,
|
||||
-- and have all of sentDirect, sentProxied, recvMsgs, etc. = 0 in server stats
|
||||
onlyProxiedSMPServers :: [SMPServerSummary],
|
||||
-- currently used XFTP servers are those with Just in sessions in XFTPServerSummary,
|
||||
-- and/or have upload/download/deletion in progress;
|
||||
-- all other servers would fall into previously used servers category
|
||||
currentlyUsedXFTPServers :: [XFTPServerSummary],
|
||||
-- previously used XFTP servers are those with Nothing in sessions and don't have any process in progress
|
||||
previouslyUsedXFTPServers :: [XFTPServerSummary]
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data SMPServerSummary = SMPServerSummary
|
||||
{ smpServer :: SMPServer,
|
||||
-- known:
|
||||
-- for simplicity always Nothing in totalServersSummary - allows us to load configured servers only for current user,
|
||||
-- and also unnecessary unless we want to add navigation to other users servers settings;
|
||||
-- always Just in currentUserServers - True if server is in list of user servers, otherwise False;
|
||||
-- True - allows to navigate to server settings, False - allows to add server to configured as known (SEKnown)
|
||||
known :: Maybe Bool,
|
||||
sessions :: Maybe ServerSessions,
|
||||
subs :: Maybe SMPServerSubs,
|
||||
-- stats:
|
||||
-- even if sessions and subs are Nothing, stats can be Just - server could be used earlier in session,
|
||||
-- or in previous sessions and stats for it were restored; server would fall into a category of
|
||||
-- previously used or only proxied servers - see ServersSummary above
|
||||
stats :: Maybe AgentSMPServerStatsData
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data XFTPServerSummary = XFTPServerSummary
|
||||
{ xftpServer :: XFTPServer,
|
||||
known :: Maybe Bool, -- same as for SMPServerSummary
|
||||
sessions :: Maybe ServerSessions,
|
||||
stats :: Maybe AgentXFTPServerStatsData,
|
||||
rcvInProgress :: Bool,
|
||||
sndInProgress :: Bool,
|
||||
delInProgress :: Bool
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
-- Maps AgentServersSummary to PresentedServersSummary:
|
||||
-- - currentUserServers is for currentUser;
|
||||
-- - users are passed to exclude hidden users from totalServersSummary;
|
||||
-- - if currentUser is hidden, it should be accounted in totalServersSummary;
|
||||
-- - known is set only in user level summaries based on passed userSMPSrvs and userXFTPSrvs
|
||||
toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> [SMPServer] -> [XFTPServer] -> PresentedServersSummary
|
||||
toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs = do
|
||||
let (userSMPSrvsSumms, allSMPSrvsSumms) = accSMPSrvsSummaries
|
||||
(userSMPCurr, userSMPPrev, userSMPProx) = smpSummsIntoCategories userSMPSrvsSumms
|
||||
(allSMPCurr, allSMPPrev, allSMPProx) = smpSummsIntoCategories allSMPSrvsSumms
|
||||
(userXFTPSrvsSumms, allXFTPSrvsSumms) = accXFTPSrvsSummaries
|
||||
(userXFTPCurr, userXFTPPrev) = xftpSummsIntoCategories userXFTPSrvsSumms
|
||||
(allXFTPCurr, allXFTPPrev) = xftpSummsIntoCategories allXFTPSrvsSumms
|
||||
PresentedServersSummary
|
||||
{ statsStartedAt,
|
||||
currentUserServers =
|
||||
ServersSummary
|
||||
{ currentlyUsedSMPServers = userSMPCurr,
|
||||
previouslyUsedSMPServers = userSMPPrev,
|
||||
onlyProxiedSMPServers = userSMPProx,
|
||||
currentlyUsedXFTPServers = userXFTPCurr,
|
||||
previouslyUsedXFTPServers = userXFTPPrev
|
||||
},
|
||||
allUsersServers =
|
||||
ServersSummary
|
||||
{ currentlyUsedSMPServers = allSMPCurr,
|
||||
previouslyUsedSMPServers = allSMPPrev,
|
||||
onlyProxiedSMPServers = allSMPProx,
|
||||
currentlyUsedXFTPServers = allXFTPCurr,
|
||||
previouslyUsedXFTPServers = allXFTPPrev
|
||||
}
|
||||
}
|
||||
where
|
||||
AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary
|
||||
countUserInAll auId = auId == aUserId currentUser || auId `notElem` hiddenUserIds
|
||||
hiddenUserIds = map aUserId $ filter (isJust . viewPwdHash) users
|
||||
smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary])
|
||||
smpSummsIntoCategories = foldr partitionSummary ([], [], [])
|
||||
where
|
||||
partitionSummary srvSumm (curr, prev, prox)
|
||||
| isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox)
|
||||
| isPreviouslyUsed srvSumm = (curr, srvSumm : prev, prox)
|
||||
| otherwise = (curr, prev, srvSumm : prox)
|
||||
isCurrentlyUsed SMPServerSummary {sessions, subs} = isJust sessions || isJust subs
|
||||
isPreviouslyUsed SMPServerSummary {stats} = case stats of
|
||||
Nothing -> False
|
||||
-- add connCompleted, connDeleted?
|
||||
-- check: should connCompleted be counted for proxy? is it?
|
||||
Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} ->
|
||||
_sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0
|
||||
xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary])
|
||||
xftpSummsIntoCategories = foldr partitionSummary ([], [])
|
||||
where
|
||||
partitionSummary srvSumm (curr, prev)
|
||||
| isCurrentlyUsed srvSumm = (srvSumm : curr, prev)
|
||||
| otherwise = (curr, srvSumm : prev)
|
||||
isCurrentlyUsed XFTPServerSummary {sessions, rcvInProgress, sndInProgress, delInProgress} =
|
||||
isJust sessions || rcvInProgress || sndInProgress || delInProgress
|
||||
accSMPSrvsSummaries :: (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary)
|
||||
accSMPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs2 smpServersStats
|
||||
where
|
||||
summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) smpServersSessions
|
||||
summs2 = M.foldrWithKey' (addServerData addSubs) summs1 smpServersSubs
|
||||
addServerData ::
|
||||
(a -> SMPServerSummary -> SMPServerSummary) ->
|
||||
(UserId, SMPServer) ->
|
||||
a ->
|
||||
(Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) ->
|
||||
(Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary)
|
||||
addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms')
|
||||
where
|
||||
userSumms'
|
||||
| userId == aUserId currentUser = alterSumms newUserSummary userSumms
|
||||
| otherwise = userSumms
|
||||
allUsersSumms'
|
||||
| countUserInAll userId = alterSumms newSummary allUsersSumms
|
||||
| otherwise = allUsersSumms
|
||||
alterSumms n = M.alter (Just . addData d . fromMaybe n) srv
|
||||
newUserSummary = (newSummary :: SMPServerSummary) {known = Just $ srv `elem` userSMPSrvs}
|
||||
newSummary =
|
||||
SMPServerSummary
|
||||
{ smpServer = srv,
|
||||
known = Nothing,
|
||||
sessions = Nothing,
|
||||
subs = Nothing,
|
||||
stats = Nothing
|
||||
}
|
||||
addSessions :: ServerSessions -> SMPServerSummary -> SMPServerSummary
|
||||
addSessions s summ@SMPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions}
|
||||
addSubs :: SMPServerSubs -> SMPServerSummary -> SMPServerSummary
|
||||
addSubs s summ@SMPServerSummary {subs} = summ {subs = Just $ maybe s (s `addSMPSubs`) subs}
|
||||
addStats :: AgentSMPServerStatsData -> SMPServerSummary -> SMPServerSummary
|
||||
addStats s summ@SMPServerSummary {stats} = summ {stats = Just $ maybe s (s `addSMPStats`) stats}
|
||||
accXFTPSrvsSummaries :: (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary)
|
||||
accXFTPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs1 xftpServersStats
|
||||
where
|
||||
summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) xftpServersSessions
|
||||
addServerData ::
|
||||
(a -> XFTPServerSummary -> XFTPServerSummary) ->
|
||||
(UserId, XFTPServer) ->
|
||||
a ->
|
||||
(Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) ->
|
||||
(Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary)
|
||||
addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms')
|
||||
where
|
||||
userSumms'
|
||||
| userId == aUserId currentUser = alterSumms newUserSummary userSumms
|
||||
| otherwise = userSumms
|
||||
allUsersSumms'
|
||||
| countUserInAll userId = alterSumms newSummary allUsersSumms
|
||||
| otherwise = allUsersSumms
|
||||
alterSumms n = M.alter (Just . addData d . fromMaybe n) srv
|
||||
newUserSummary = (newSummary :: XFTPServerSummary) {known = Just $ srv `elem` userXFTPSrvs}
|
||||
newSummary =
|
||||
XFTPServerSummary
|
||||
{ xftpServer = srv,
|
||||
known = Nothing,
|
||||
sessions = Nothing,
|
||||
stats = Nothing,
|
||||
rcvInProgress = srv `elem` xftpRcvInProgress,
|
||||
sndInProgress = srv `elem` xftpSndInProgress,
|
||||
delInProgress = srv `elem` xftpDelInProgress
|
||||
}
|
||||
addSessions :: ServerSessions -> XFTPServerSummary -> XFTPServerSummary
|
||||
addSessions s summ@XFTPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions}
|
||||
addStats :: AgentXFTPServerStatsData -> XFTPServerSummary -> XFTPServerSummary
|
||||
addStats s summ@XFTPServerSummary {stats} = summ {stats = Just $ maybe s (s `addXFTPStats`) stats}
|
||||
addServerSessions :: ServerSessions -> ServerSessions -> ServerSessions
|
||||
addServerSessions ss1 ss2 =
|
||||
ServerSessions
|
||||
{ ssConnected = ssConnected ss1 + ssConnected ss2,
|
||||
ssErrors = ssErrors ss1 + ssErrors ss2,
|
||||
ssConnecting = ssConnecting ss1 + ssConnecting ss2
|
||||
}
|
||||
addSMPSubs :: SMPServerSubs -> SMPServerSubs -> SMPServerSubs
|
||||
addSMPSubs ss1 ss2 =
|
||||
SMPServerSubs
|
||||
{ ssActive = ssActive ss1 + ssActive ss2,
|
||||
ssPending = ssPending ss1 + ssPending ss2
|
||||
}
|
||||
addSMPStats :: AgentSMPServerStatsData -> AgentSMPServerStatsData -> AgentSMPServerStatsData
|
||||
addSMPStats sd1 sd2 =
|
||||
AgentSMPServerStatsData
|
||||
{ _sentDirect = _sentDirect sd1 + _sentDirect sd2,
|
||||
_sentViaProxy = _sentViaProxy sd1 + _sentViaProxy sd2,
|
||||
_sentProxied = _sentProxied sd1 + _sentProxied sd2,
|
||||
_sentDirectAttempts = _sentDirectAttempts sd1 + _sentDirectAttempts sd2,
|
||||
_sentViaProxyAttempts = _sentViaProxyAttempts sd1 + _sentViaProxyAttempts sd2,
|
||||
_sentProxiedAttempts = _sentProxiedAttempts sd1 + _sentProxiedAttempts sd2,
|
||||
_sentAuthErrs = _sentAuthErrs sd1 + _sentAuthErrs sd2,
|
||||
_sentQuotaErrs = _sentQuotaErrs sd1 + _sentQuotaErrs sd2,
|
||||
_sentExpiredErrs = _sentExpiredErrs sd1 + _sentExpiredErrs sd2,
|
||||
_sentOtherErrs = _sentOtherErrs sd1 + _sentOtherErrs sd2,
|
||||
_recvMsgs = _recvMsgs sd1 + _recvMsgs sd2,
|
||||
_recvDuplicates = _recvDuplicates sd1 + _recvDuplicates sd2,
|
||||
_recvCryptoErrs = _recvCryptoErrs sd1 + _recvCryptoErrs sd2,
|
||||
_recvErrs = _recvErrs sd1 + _recvErrs sd2,
|
||||
_connCreated = _connCreated sd1 + _connCreated sd2,
|
||||
_connSecured = _connSecured sd1 + _connSecured sd2,
|
||||
_connCompleted = _connCompleted sd1 + _connCompleted sd2,
|
||||
_connDeleted = _connDeleted sd1 + _connDeleted sd2,
|
||||
_connSubscribed = _connSubscribed sd1 + _connSubscribed sd2,
|
||||
_connSubAttempts = _connSubAttempts sd1 + _connSubAttempts sd2,
|
||||
_connSubErrs = _connSubErrs sd1 + _connSubErrs sd2
|
||||
}
|
||||
addXFTPStats :: AgentXFTPServerStatsData -> AgentXFTPServerStatsData -> AgentXFTPServerStatsData
|
||||
addXFTPStats sd1 sd2 =
|
||||
AgentXFTPServerStatsData
|
||||
{ _uploads = _uploads sd1 + _uploads sd2,
|
||||
_uploadAttempts = _uploadAttempts sd1 + _uploadAttempts sd2,
|
||||
_uploadErrs = _uploadErrs sd1 + _uploadErrs sd2,
|
||||
_downloads = _downloads sd1 + _downloads sd2,
|
||||
_downloadAttempts = _downloadAttempts sd1 + _downloadAttempts sd2,
|
||||
_downloadAuthErrs = _downloadAuthErrs sd1 + _downloadAuthErrs sd2,
|
||||
_downloadErrs = _downloadErrs sd1 + _downloadErrs sd2,
|
||||
_deletions = _deletions sd1 + _deletions sd2,
|
||||
_deleteAttempts = _deleteAttempts sd1 + _deleteAttempts sd2,
|
||||
_deleteErrs = _deleteErrs sd1 + _deleteErrs sd2
|
||||
}
|
||||
|
||||
$(J.deriveJSON defaultJSON ''SMPServerSummary)
|
||||
|
||||
$(J.deriveJSON defaultJSON ''XFTPServerSummary)
|
||||
|
||||
$(J.deriveJSON defaultJSON ''ServersSummary)
|
||||
|
||||
$(J.deriveJSON defaultJSON ''PresentedServersSummary)
|
||||
@@ -523,9 +523,10 @@ getProtocolServers db User {userId} =
|
||||
(userId, decodeLatin1 $ strEncode protocol)
|
||||
where
|
||||
protocol = protocolTypeI @p
|
||||
toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> ServerCfg p
|
||||
toServerCfg (host, port, keyHash, auth_, preset, tested, enabled) =
|
||||
toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Int) -> ServerCfg p
|
||||
toServerCfg (host, port, keyHash, auth_, preset, tested, enabledInt) =
|
||||
let server = ProtoServerWithAuth (ProtocolServer protocol host port keyHash) (BasicAuth . encodeUtf8 <$> auth_)
|
||||
enabled = toServerEnabled enabledInt
|
||||
in ServerCfg {server, preset, tested, enabled}
|
||||
|
||||
overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO ()
|
||||
@@ -542,7 +543,7 @@ overwriteProtocolServers db User {userId} servers =
|
||||
(protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs))
|
||||
((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, fromServerEnabled enabled, userId, currentTs, currentTs))
|
||||
pure $ Right ()
|
||||
where
|
||||
protocol = decodeLatin1 $ strEncode $ protocolTypeI @p
|
||||
|
||||
@@ -1632,10 +1632,42 @@ data ServerCfg p = ServerCfg
|
||||
{ server :: ProtoServerWithAuth p,
|
||||
preset :: Bool,
|
||||
tested :: Maybe Bool,
|
||||
enabled :: Bool
|
||||
enabled :: ServerEnabled
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data ServerEnabled
|
||||
= SEDisabled
|
||||
| SEEnabled
|
||||
| -- server is marked as known, but it's not in the list of configured servers;
|
||||
-- e.g., it may be added via an unknown server dialogue and user didn't manually configure it,
|
||||
-- meaning server wasn't tested (or at least such option wasn't presented in UI)
|
||||
-- and it may be inoperable for user due to server password
|
||||
SEKnown
|
||||
deriving (Eq, Show)
|
||||
|
||||
pattern DBSEDisabled :: Int
|
||||
pattern DBSEDisabled = 0
|
||||
|
||||
pattern DBSEEnabled :: Int
|
||||
pattern DBSEEnabled = 1
|
||||
|
||||
pattern DBSEKnown :: Int
|
||||
pattern DBSEKnown = 2
|
||||
|
||||
toServerEnabled :: Int -> ServerEnabled
|
||||
toServerEnabled = \case
|
||||
DBSEDisabled -> SEDisabled
|
||||
DBSEEnabled -> SEEnabled
|
||||
DBSEKnown -> SEKnown
|
||||
_ -> SEDisabled
|
||||
|
||||
fromServerEnabled :: ServerEnabled -> Int
|
||||
fromServerEnabled = \case
|
||||
SEDisabled -> DBSEDisabled
|
||||
SEEnabled -> DBSEEnabled
|
||||
SEKnown -> DBSEKnown
|
||||
|
||||
data ChatVersion
|
||||
|
||||
instance VersionScope ChatVersion
|
||||
@@ -1764,6 +1796,8 @@ $(JQ.deriveJSON defaultJSON ''ContactRef)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''NoteFolder)
|
||||
|
||||
$(JQ.deriveJSON (enumJSON $ dropPrefix "SE") ''ServerEnabled)
|
||||
|
||||
instance ProtocolTypeI p => ToJSON (ServerCfg p) where
|
||||
toEncoding = $(JQ.mkToEncoding defaultJSON ''ServerCfg)
|
||||
toJSON = $(JQ.mkToJSON defaultJSON ''ServerCfg)
|
||||
|
||||
@@ -36,7 +36,7 @@ import Simplex.Chat.Types.Shared
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>))
|
||||
|
||||
data ChatFeature
|
||||
= CFTimedMessages
|
||||
|
||||
@@ -18,6 +18,7 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
||||
import Simplex.Chat.Types.Util
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_)
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON)
|
||||
|
||||
data UITheme = UITheme
|
||||
{ themeId :: Text,
|
||||
|
||||
@@ -2,26 +2,15 @@
|
||||
|
||||
module Simplex.Chat.Types.Util where
|
||||
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Types as JT
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Data.Typeable
|
||||
import Database.SQLite.Simple (ResultError (..), SQLData (..))
|
||||
import Database.SQLite.Simple.FromField (FieldParser, returnError)
|
||||
import Database.SQLite.Simple.Internal (Field (..))
|
||||
import Database.SQLite.Simple.Ok (Ok (Ok))
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||
|
||||
encodeJSON :: ToJSON a => a -> Text
|
||||
encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode
|
||||
|
||||
decodeJSON :: FromJSON a => Text -> Maybe a
|
||||
decodeJSON = J.decode . LB.fromStrict . encodeUtf8
|
||||
|
||||
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
||||
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode
|
||||
|
||||
@@ -54,7 +54,6 @@ import qualified Simplex.FileTransfer.Transport as XFTP
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..))
|
||||
import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..))
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP))
|
||||
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
|
||||
import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..))
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
@@ -366,6 +365,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
"chat entity locks: " <> viewJSON chatEntityLocks,
|
||||
"agent locks: " <> viewJSON agentLocks
|
||||
]
|
||||
CRAgentServersSummary u serversSummary -> ttyUser u ["agent servers summary: " <> viewJSON serversSummary]
|
||||
CRAgentStats stats -> map (plain . intercalate ",") stats
|
||||
CRAgentSubs {activeSubs, pendingSubs, removedSubs} ->
|
||||
[plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", pending = " <> show (sum pendingSubs) <> ", removed = " <> show (sum $ M.map length removedSubs)]
|
||||
|
||||
@@ -17,7 +17,7 @@ import Simplex.Chat.Store.Shared (createContact)
|
||||
import Simplex.Chat.Types (ConnStatus (..), Profile (..))
|
||||
import Simplex.Chat.Types.Shared (GroupMemberRole (..))
|
||||
import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Types.Util (encodeJSON)
|
||||
import Simplex.Messaging.Util (encodeJSON)
|
||||
import Simplex.Messaging.Encoding.String (StrEncoding (..))
|
||||
import System.Directory (copyFile, createDirectoryIfMissing)
|
||||
import Test.Hspec hiding (it)
|
||||
|
||||
Reference in New Issue
Block a user